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).