Gateway security
The gateway is the public edge. Everything on this page is about one question: which decisions does a client get to influence, and which does the deployment make.
Tenancy is decided by the deployment
Section titled “Tenancy is decided by the deployment”A domainmap binds hosts to a CEPT tuple. The tenancy headers a backend reads are stamped by the gateway from that mapping, so they describe where the request arrived, not what it asked for.
domainmaps: - name: acme-prod domains: [app.acme.com] customer: acme environment: prod product: forge tenant: main- The gateway derives the routing domain from the request’s own
Host. - The mapper resolves that domain and sets
X-Customer,X-Product,X-EnvandX-Tenant. - A domain in no domainmap is answered
400rather than served against a default.
Because the tuple comes from the mapping, a client cannot select a tenant by sending a header. Any tenancy headers arriving from outside are replaced by the mapper’s own.
Forwarded headers
Section titled “Forwarded headers”X-Forwarded-* is a claim about a request’s origin made by whatever sent it, so the gateway believes
it only from a peer it was told to believe.
| Peer | Behaviour |
|---|---|
Listed in the entrypoint’s trustedips | X-Forwarded-Host becomes the routing domain, and X-Real-Ip is carried through |
| Anything else | The origin host and client IP are captured internally, then the whole X-Forwarded-* set is deleted before the request goes further |
The capture step is why the gateway still knows the true origin behind a trusted proxy that rewrites
Host, and the deletion step is why an untrusted client cannot use the same headers to pick a
routing domain.
If the gateway sits behind a load balancer or another proxy, that peer’s address belongs in
trustedips. Keep the list to the addresses that actually front the gateway.
Cross-tenant access
Section titled “Cross-tenant access”An operator sometimes needs to act against another tenant. That is X-Target-Tenant, and it is
constrained twice:
- It is honoured only on a domainmap explicitly marked
superadmin. On any other domain the header is refused and the refusal is logged. - When it is honoured, the gateway stamps
X-SuperAdmin: trueso the backend can tell an operator-initiated request from an ordinary one and audit it as such.
Mark as few domainmaps superadmin as the operation needs, and treat those hostnames as
administrative surface.
Token verification
Section titled “Token verification”The kisai-auth middleware verifies bearer tokens locally, against the public key in
jwt_public_key. The identity service issues tokens and is the login redirect target; it is not
consulted per request, so a request is not served on the strength of an unverified token when that
service is unreachable.
endpoints: - name: api path: { prefix: /api } backend: api-svc middlewares: [kisai-auth, customer-domain-mapper]- A request without a valid token is redirected to
loginurlwhen one is configured, and answered403otherwise. publicPathsregexes exempt open paths. Keep them narrow and anchored: a pattern such as/healthmatches more than a pattern such as^/health$.- Rotate
jwt_public_keywith the issuer.
Authentication at the edge is a filter, not a substitute for the entity access rules each service applies. A request that passes here still meets the callee’s own authorization.
Header expressions
Section titled “Header expressions”Header values accept ${…} templates resolved per request. Two properties matter for safety:
- CRLF in a rendered value is stripped, so a value carrying a newline cannot split a header or inject another one. This is the defence against request smuggling through a templated header.
- A missing variable renders empty, and an empty header value strips the header. A template that
does not resolve removes the header rather than forwarding a literal
${…}for a backend to misread.
Values are populated by middlewares that run earlier in the chain, so place customer-domain-mapper
and kisai-auth before the header block that reads ${tenant.*} or ${auth.*}.
Hardening from upstream
Section titled “Hardening from upstream”Staying close to upstream Traefik means edge hardening arrives with the version bump. The current build denies requests with an opaque request target, and stops h2c upgrade headers and request trailers being forwarded to a backend.
Mutual TLS
Section titled “Mutual TLS”zerotrust: true turns on two things at once:
- The internal listener requires a client certificate.
- The gateway uses mTLS on its own leg to each backend.
It is independent of public TLS. A route can terminate a public certificate whether or not zerotrust is on, and a deployment can run zerotrust internally while serving the internet normally.
The TLS option names default and kisaitlsinternal are reserved, and
passthrough refuses writes to them, so the hardened defaults
that generated routes rely on stay as configured. Defining new option names is the supported way to
add a stricter or a more permissive profile for a specific route.
The management surfaces
Section titled “The management surfaces”| Surface | Posture |
|---|---|
| Metrics and profiling | Its own listener, bound to loopback by default. Never put it on the data plane |
| Router dashboard | Expose it as an ordinary route behind kisai-auth. The unauthenticated dashboard mode is refused at boot |
| Backend TLS verification | The global switch is protected. Scope any exception to one named transport |
| Router plugins | Interpreted Go inside the gateway process, so the passthrough path for them is closed |
Layer 4
Section titled “Layer 4”TCP and UDP entrypoints carry bytes, not requests, so there is no request for the middleware chain to act on and no per-request tenant attribution. Give an L4 port one tenant, by binding a listener per tenant. See Entrypoints.
Before you put it on the internet
Section titled “Before you put it on the internet”- Every public hostname is in a domainmap, and unmapped hosts are expected to answer
400. -
trustedipslists only the proxies that actually front the gateway. -
superadminis set on the fewest domainmaps possible, and those hostnames are treated as admin surface. -
jwt_public_keymatches the current issuer key, and rotation is planned. -
publicPathspatterns are anchored and reviewed. - The metrics listener is on loopback or an internal interface.
- The dashboard, if exposed at all, is behind
kisai-auth. - TLS is issued and renewing, and the ACME storage path is writable and backed up.
-
zerotrustmatches the mesh posture you intend. - Any passthrough block has been reviewed as configuration a person wrote by hand.
- The running binary’s Traefik version is the one you expect:
gateway.svc version.
See also
Section titled “See also”- Entrypoints: trusted IPs and listener boundaries
- Passthrough: the protected paths and why
- Operations: reload behaviour and failure modes