Skip to main content

api-gateway (Kong) — Application Logic

Status: populated Owner: TBD (Platform / SRE) Last updated: 2026-04-17 Companion: SERVICE_OVERVIEW · SECURITY_MODEL · Service Template

1. Purpose​

Describe the request flow through Kong, the plugin chain ordering, error shaping, and the behaviour of the single custom Ghasi plugin. Kong has no application logic in the DDD sense; what follows is the edge middleware pipeline.

2. Request flow​

3. Plugin chain (logical order)​

Kong executes plugins in phase order (access, rewrite, header_filter, body_filter, log). Within a phase, plugins run by configured priority. The logical chain for a customer SMS-send request:

  1. TLS / SNI — Cloudflare (edge) then Kong (origin) terminate TLS. Minimum TLS 1.2; 1.3 preferred.
  2. Route match — host + path + method; rejects unknown routes with 404 Not Found in Kong's default problem+json shape.
  3. jwt or key-auth — authentication. Failure → 401 Unauthorized.
  4. Custom ghasi-api-key-lookup (key-auth routes only, when enabled) — resolves the API key to account_id + scopes + tier via cached auth-service call; injects X-Account-Id, X-Api-Key-Id, X-Tier headers.
  5. ip-restriction — admin and partner routes only. Failure → 403 Forbidden.
  6. request-size-limiting — default 64 KB for /v1/sms/send; 2 MB for /v1/sms/bulk. Failure → 413 Payload Too Large.
  7. bot-detection — blocks obvious bot UAs on public routes. Failure → 403 Forbidden.
  8. rate-limiting-advanced — Redis-backed counters keyed by consumer + window. Failure → 429 Too Many Requests with Retry-After.
  9. request-transformer — remove X-Powered-By, inject X-Gateway-Source: kong, propagate X-Tenant-Id.
  10. correlation-id — inject X-Request-Id if missing (UUIDv7); forward to upstream.
  11. opentelemetry — start span; inject traceparent into upstream request.
  12. Proxy to upstream; upstream logic (validation, idempotency, NATS publish) runs in sms-orchestrator.
  13. http-log — headers and metadata only to Loki; body logging disabled.

4. Rate limiting​

Rate limiting is the single most load-bearing edge concern for a telecom SMS platform. Kong's rate-limiting-advanced plugin is configured with a Redis cluster backend.

TierKeyWindowDefault limit
Globalkong:rl:global1 s5 000 req/s (burst guard)
Per API keykong:rl:key:<api_key_id>1 s / 60 sPer-tier (from auth-service)
Per accountkong:rl:acct:<account_id>60 sPer-tier aggregated
Per operator (downstream hint)kong:rl:op:<operator_id>1 sConfigured in operator-management-service; read by Kong at config time

Fail mode: fail-closed for /v1/sms/send (safer to reject than double-charge). Fail-open for read-only endpoints (/v1/sms/{id}, /v1/analytics/*). Configured per route.

Headers returned on throttled responses:

  • Retry-After: <seconds>
  • X-RateLimit-Limit-<window>, X-RateLimit-Remaining-<window>, X-RateLimit-Reset-<window>

5. Custom plugin: ghasi-api-key-lookup​

Purpose: Resolve an X-Api-Key header to an account context without pre-provisioning a Kong Consumer row per customer. Useful when API-key issuance is high-volume or churny (customer-initiated rotation).

Behaviour:

  1. Read X-Api-Key header. If absent → defer to key-auth plugin (fail).
  2. Hash the key (sha256), check in-memory LRU cache (TTL 60 s, max 10 000 entries).
  3. On miss → GET {auth-service}/internal/api-keys/resolve?hash=<hash> with service-to-service auth (mTLS or service token).
  4. auth-service returns { accountId, apiKeyId, scopes[], tier, status } or 404.
  5. On status != "active" or 404 → reject with 401 invalid_api_key.
  6. On success → populate Kong context:
    • kong.ctx.shared.consumer = { username: "csm-<accountId>", custom_id: "<accountId>" }
    • Inject headers: X-Account-Id, X-Api-Key-Id, X-Tier.
    • Attach rate-limit key overrides via shared context for rate-limiting-advanced.
  7. Emit Prometheus counters: ghasi_api_key_lookup_total{result="hit|miss|invalid"} and histogram ghasi_api_key_lookup_latency_seconds.

Failure modes:

  • auth-service unavailable: plugin returns 503 with Retry-After: 1 (do not fail-open — an unauthenticated request must never reach sms-orchestrator).
  • Cache hot, auth-service momentarily down: serve from cache while TTL unexpired; log a warning.

Implementation note: Code lives in the application monorepo at ops/kong/plugins/ghasi-api-key-lookup/. Language: Lua (Kong plugin SDK) or Go (via Kong go-pdk) — SRE choice. This doc is the contract.

6. Error shaping​

Kong's default error format is terse JSON ({ "message": "..." }). We configure the response-transformer-advanced (or a small error-handler plugin) so edge errors use Problem+json aligned with docs/standards/ERROR_CODES.md:

{
"type": "https://errors.ghasi.io/gateway/rate-limited",
"title": "Too Many Requests",
"status": 429,
"detail": "Per-key limit of 10 req/s exceeded.",
"instance": "urn:gw:<X-Request-Id>",
"retryAfter": 1
}

Upstream errors pass through unchanged — upstream services are the source of truth for their own error bodies.

7. Header contract (ingress → upstream)​

HeaderDirectionSet byNotes
Authorization: Bearer <jwt>in → upstreamClientForwarded verbatim
X-Api-KeyinClientNot forwarded upstream (stripped after auth)
X-Account-Idout (kong → upstream)ghasi-api-key-lookup or jwt pluginTrusted by upstream only when from Kong
X-Api-Key-Idoutghasi-api-key-lookupFor audit
X-Tieroutghasi-api-key-lookup or JWT claimFor billing/ratelimit visibility
X-Request-Idin/outcorrelation-idGenerated if absent
traceparentin/outopentelemetryW3C trace context
X-Tenant-Idin/outClient or JWT tidPropagated
Idempotency-Keyin → upstreamClientForwarded verbatim to sms-orchestrator
X-Gateway-Source: kongoutrequest-transformerLets upstream distinguish Kong vs direct access
X-Forwarded-ForoutKong built-inChain: client, CF, kong

Upstream services must trust X-Account-Id only when X-Gateway-Source: kong is present AND the request arrived on the internal network (zero-trust: verify via mTLS or network policy). East-west traffic that bypasses Kong uses service tokens instead.

8. Idempotency​

Kong does not implement idempotency. The Idempotency-Key header is forwarded verbatim to sms-orchestrator, which owns the Redis-backed dedupe store (see services/sms-orchestrator/APPLICATION_LOGIC.md).

9. NATS publishing​

Kong does not publish NATS events. Any upstream service consuming a request publishes its own events per EVENT_SCHEMAS and its respective service docs.

10. Health and readiness​

  • /status — Kong's internal status endpoint (Admin API only, network-isolated).
  • /health (on proxy port) — a custom dummy Route returning 200 when Kong data plane is alive; scraped by the Kubernetes liveness probe.
  • Readiness probe: Kong exits unready if JWKS fetch has failed continuously for N minutes (configurable).

11. Open questions​

  • rate-limiting-advanced vs stock rate-limiting plugin — enterprise vs OSS decision.
  • Custom plugin language (Lua vs Go). Go is easier to test; Lua has lower overhead.
  • Fail-closed vs fail-open per route — explicit matrix pending (SMS endpoints closed; analytics open).