Skip to content

Gateway Hardening

The MeshFlows gateway is the single public entrypoint to the platform. This page documents the security controls applied at the edge layer: authentication, security headers, CORS, and rate limiting.

Authentication Modes

Every EdgeRoute declares an auth.mode that controls how the gateway enforces caller identity:

Mode Behaviour
none No token check. Use only for truly public endpoints.
token Bearer token required (JWT or API key). No scope enforcement.
oauth2 Bearer token required and must carry all required_scopes. Default when auth is omitted.

Only Bearer tokens are supported (JWT or API key). Basic and HMAC auth are not supported by the gateway today.

JWT Tokens

The gateway validates JWT tokens locally (HS256 signature, exp, iss, aud, sub):

  • Config env vars: IDENTITY_JWT_SECRET, IDENTITY_JWT_ISSUER, IDENTITY_JWT_AUDIENCE
  • On success: request.state.identity is set with {sub, groups, scopes, token_type: "jwt"}

By default (GATEWAY_JWT_SCOPE_MODE=token) scopes are taken from the JWT payload until the token expires.

Set GATEWAY_JWT_SCOPE_MODE=live to refresh scopes and groups from the identity database on each request (with a short cache):

Variable Default Description
GATEWAY_JWT_SCOPE_MODE token token = JWT claims only; live = refresh via identity GET /auth/me
GATEWAY_JWT_SCOPE_CACHE_TTL_SECONDS 15 Cache TTL for live scope lookups per token
IDENTITY_SERVICE_URL http://identity:8080 Required when mode is live

Revoked permissions take effect on the gateway within the cache TTL (typically 15s), not when the JWT expires.

If identity is unreachable in live mode, the gateway falls back to JWT token scopes and logs a warning (degraded mode).

Tokens are issued by the identity service at POST /auth/token.

API Keys

API keys use a mk_ prefix and are validated via the identity service:

  • The gateway calls POST /api-keys/validate on identity and caches the result for 60 seconds (reduces latency on high-frequency calls)
  • Keys are stored as SHA-256 hashes; the raw value is shown once at creation
  • Scopes work identically to JWT scopes; if a key has no explicit scopes it inherits the owner's permissions
  • Config env var: IDENTITY_SERVICE_URL
Authorization: Bearer mk_live_a1b2c3d4...

Scope Enforcement

When auth.mode: oauth2, the route's required_scopes list is checked against the caller's effective scopes:

auth:
  mode: oauth2
  required_scopes:
    - orders:read
    - orders:write

A 403 Forbidden is returned if any required scope is missing.

Public Paths

The following paths bypass authentication entirely:

  • GET /healthz
  • GET /readyz

Security Headers

Every response from the gateway includes these headers:

Header Value
Strict-Transport-Security max-age=31536000; includeSubDomains
X-Content-Type-Options nosniff
X-Frame-Options DENY
Cache-Control no-store
X-Request-ID Correlation ID (generated or forwarded from X-Request-ID request header)

CORS

CORS is configurable per deployment via the GATEWAY_CORS_ORIGINS environment variable (comma-separated list of allowed origins):

GATEWAY_CORS_ORIGINS=https://dashboard.meshflows.org,https://app.example.com
  • Preflight OPTIONS requests are handled automatically and return 204 No Content
  • If GATEWAY_CORS_ORIGINS is empty, the header is omitted (same-origin only in browsers)
  • Allowed methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
  • Allowed headers: Content-Type, Authorization, X-Request-ID

Rate Limiting

The gateway supports distributed rate limiting backed by Redis, so limits apply consistently across multiple gateway replicas.

Configuration

Rate limits are defined per EdgeRoute:

rate_limit:
  max_requests: 100
  window_seconds: 60
  key_from: subject       # ip | subject | api_key | header:<name>
key_from Behaviour
ip Limit per client IP
subject Limit per authenticated user/system (sub claim)
api_key Limit per API key
header:<name> Limit per value of a specific header (e.g. header:X-Tenant-ID)

Redis Backend

The gateway uses a fixed-window counter in Redis (INCR + EXPIRE). If Redis is unavailable, the gateway falls back to an in-memory per-pod limiter and logs a warning (at most once per 60 seconds to avoid log spam).

Config env var: REDIS_URL (e.g. redis://redis:6379/0)

Identity Public Surface

lab-identity.meshflows.org (HTTPRoute / Traefik) exposes only:

  • /healthz, /readyz
  • /auth/* (login, OAuth callbacks, MFA challenges, /auth/me)
  • /token (OAuth2 password grant)

IAM admin APIs (/users, /groups, /permissions, …), secrets, products and POST /api-keys/validate are not routed publicly. Cluster services call http://identity:8080 directly.

POST /api-keys/validate additionally requires header X-MeshFlows-Internal-Token (derived from IDENTITY_JWT_SECRET unless IDENTITY_INTERNAL_SERVICE_TOKEN is set). Disable with IDENTITY_REQUIRE_INTERNAL_TOKEN=false (dev only).

Product-Based Rate Limiting

For product-based limits see API Keys & Products. The rate_limit_product policy type resolves the caller's quota from their assigned product.


Kubernetes Secrets

Secret Key Used by
meshflows-identity-auth jwt_secret Gateway JWT validation + identity service signing
meshflows-google-oauth client_id, client_secret, redirect_uri, dashboard_callback_url Identity service Google OAuth (optional)

The meshflows-google-oauth secret is created by the deploy pipeline only when GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET CI/CD secrets are set. If absent, Google login is simply disabled.