Skip to content

Latest commit

 

History

82 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

OIDC Gateway

Release Lint Coverage Contributor-Covenant

oidc-gateway is an Envoy-based authentication and authorization gateway for services that need to accept human, machine, CI/CD, and SPIFFE workload identities through one consistent backend contract.

The gateway validates credentials at the edge, normalizes the caller into a canonical principal, evaluates route-level authorization with Casbin, and forwards only a minimal identity header to the upstream service. This lets backends avoid auth mechanism-specific code and rely on a single principal format.

What It Does

  • Accepts OIDC bearer JWTs, SPIFFE JWT-SVIDs, and SPIFFE X.509-SVID client certificates.
  • Prefers verified SPIFFE X.509-SVID identity when present and ignores bearer tokens in that case.
  • Falls back to bearer JWT authentication when no X.509-SVID identity is available.
  • Allows configured public paths such as /healthz and gRPC reflection without credentials.
  • Denies non-public requests when no supported credential is present.
  • Extracts canonical principals such as oidc:dex:alice, oidc:github:repo:org/repo:workflow:deploy.yml:ref:refs/heads/main, and spiffe:spiffe://example.org/ns/default/sa/backend.
  • Authorizes those principals against YAML-defined roles and allowed HTTP or gRPC paths.
  • Sends upstream services exactly one configured identity header, defaulting to x-auth-principal.

Request Flow

client
  |
  |  bearer JWT or SPIFFE X.509-SVID client certificate
  v
Envoy
  |  validates bearer JWTs with jwt_authn
  |  validates downstream mTLS when SPIFFE X.509-SVID is enabled
  |  strips client-supplied x-jwt-payload and configured principal header
  v
oidc-gateway ext_authz server
  |  prefers X.509 identity, otherwise reads verified x-jwt-payload
  |  normalizes identity to <auth-family>:<canonical-principal>
  |  evaluates Casbin RBAC
  v
backend service
  |
  |  receives configured principal header only when authorization succeeds

For non-public paths, the precedence is:

  1. SPIFFE X.509-SVID from verified downstream mTLS.
  2. OIDC JWT or SPIFFE JWT-SVID from Envoy jwt_authn.
  3. Deny when neither identity source is available.

Repository Layout

  • cmd/oidc-gateway: runnable ext_authz server, Dockerfile, local Docker Compose test environment, and local test configs.
  • authzserver: Envoy ext_authz implementation, payload extraction, config validation, deny list handling, and Casbin RBAC integration.
  • identity: lightweight public Go module for parsing and formatting canonical identity principals.
  • install/charts/oidc-gateway: Helm chart that deploys Envoy and the authorization server.
  • Taskfile.yml: build, lint, test, license, dependency, Helm, and release automation.

Canonical Principal Format

Every authenticated caller is represented as:

<auth-family>:<canonical-principal>

Supported auth families:

  • oidc: OIDC-style identity from a configured issuer alias.
  • spiffe: SPIFFE identity from either JWT-SVID or X.509-SVID.

Examples:

oidc:dex:alice
oidc:dex:sync-service
oidc:github:repo:org/repo:workflow:deploy.yml:ref:refs/heads/main
spiffe:spiffe://example.org/ns/default/sa/backend

OIDC issuers use a stable configured providerKey, so policy rules do not need to embed long issuer URLs. SPIFFE principals keep the full SPIFFE ID.

GitHub workflow wildcard rules are intentionally strict. A GitHub wildcard may contain exactly one *, it must be the final character, and it is only supported inside the branch ref segment:

oidc:github:repo:org/repo:workflow:deploy.yml:ref:refs/heads/release-*

Backend Header Contract

Backends should consume only the configured principal header. By default this is:

x-auth-principal: <auth-family>:<canonical-principal>

Clients must not set this header themselves. Envoy strips client-supplied principal headers, and the ext_authz server sets the configured header only after successful authentication and authorization.

x-jwt-payload is an internal Envoy-to-ext_authz header. In production, Envoy sets it after jwt_authn validates a bearer JWT. It is not a client API.

Authorization Config

The ext_authz server loads YAML from CONFIG_PATH, defaulting to /etc/oidc-gateway/config.yaml.

claims:
  principalClaim: "sub"
  emailClaimPath: "email"

headers:
  authPrincipal: "x-auth-principal"

issuers:
  - providerKey: "dex"
    provider: "https://dex.example.com"
    authFamily: "oidc"
  - providerKey: "github"
    provider: "https://token.actions.githubusercontent.com"
    authFamily: "oidc"
  - provider: "https://spire-oidc.example.org"
    authFamily: "spiffe"

denyList:
  - "oidc:dex:blocked@example.com"

publicPaths:
  - "/healthz"
  - "/grpc.reflection"

roles:
  admin:
    allowedMethods: ["*"]
    principals:
      - "oidc:dex:admin@example.com"
  workloads:
    allowedMethods:
      - "/example.service.v1.ExampleService/Read"
    principals:
      - "spiffe:*"
      - "oidc:github:repo:org/repo:workflow:deploy.yml:ref:refs/heads/*"

Important fields:

  • claims.principalClaim: JWT claim used as the default OIDC principal value.
  • claims.emailClaimPath: optional claim path used for email deny-list matching.
  • headers.authPrincipal: upstream identity header emitted by ext_authz. Defaults to x-auth-principal; configure the same value in Envoy stripping rules so clients cannot spoof it.
  • issuers[].provider: issuer URL found in the JWT iss claim.
  • issuers[].providerKey: stable alias used in oidc:<providerKey>:... principals. It is required for OIDC issuers.
  • issuers[].authFamily: oidc or spiffe. SPIFFE JWT-SVID issuers produce spiffe:<spiffe-id> principals.
  • denyList: principals or email claim values that are always denied.
  • publicPaths: defense-in-depth public path list for requests that reach the authz server. Envoy also disables authz for known public routes.
  • roles: role definitions. Each role has allowedMethods and canonical principals.

Helm Deployment

The Helm chart deploys two main workloads:

  • Envoy, exposed through one or more configured envoy.endpoints, and configured with jwt_authn, ext_authz, optional SPIFFE downstream mTLS, and optional SPIFFE upstream mTLS.
  • The oidc-gateway authorization server, exposed internally on authServer.service.port.

Install from the published OCI chart:

helm install oidc-gateway oci://ghcr.io/agntcy/oidc-gateway/helm-charts/oidc-gateway \
  --version <version> \
  -f values.yaml

The most important values to set are:

  • envoy.backend.address and envoy.backend.port: upstream backend service.
  • envoy.endpoints.oidc: OIDC/JWT listener and service port.
  • envoy.endpoints.mtls: optional SPIFFE X.509-SVID listener and service port.
  • envoy.oidc.issuers: generic OIDC or SPIFFE JWT-SVID issuers for Envoy jwt_authn.
  • envoy.oidc.github: optional GitHub Actions OIDC provider shortcut.
  • envoy.spiffe.enabled: enables SPIFFE SDS and upstream mTLS to the backend.
  • ingress.oidc: optional OIDC/JWT ingress, normally with gRPC TLS termination.
  • ingress.mtls: optional SPIFFE X.509-SVID ingress, normally with TLS passthrough.
  • authServer.oidc: renders the authorization config consumed by the ext_authz server, including the principal header name Envoy strips from client requests.

Minimal OIDC/JWT endpoint example:

envoy:
  endpoints:
    oidc:
      enabled: true
      port: 8080
      servicePort: 8080
    mtls:
      enabled: false
  backend:
    address: "directory.default.svc.cluster.local"
    port: 8888
  oidc:
    issuers:
      - name: dex
        enabled: true
        issuer: "https://dex.example.com"
        jwksUri: "https://dex.example.com/.well-known/jwks.json"
        jwksHost: "dex.example.com"
  spiffe:
    enabled: true
    trustDomain: example.org

authServer:
  oidc:
    headers:
      authPrincipal: "x-auth-principal"
    issuers:
      - providerKey: "dex"
        provider: "https://dex.example.com"
        authFamily: "oidc"
    roles:
      admin:
        allowedMethods: ["*"]
        principals:
          - "oidc:dex:admin@example.com"

Dual endpoint example:

envoy:
  endpoints:
    oidc:
      enabled: true
      port: 8080
      servicePort: 8080
      downstreamTls:
        enabled: false
    mtls:
      enabled: true
      port: 8443
      servicePort: 8443
      downstreamTls:
        enabled: true
        requireClientCertificate: true
  spiffe:
    enabled: true
    trustDomain: example.org

ingress:
  oidc:
    enabled: true
    host: gateway.example.com
    annotations:
      nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
  mtls:
    enabled: true
    host: mtls-gateway.example.com
    annotations:
      nginx.ingress.kubernetes.io/ssl-passthrough: "true"
      nginx.ingress.kubernetes.io/backend-protocol: "GRPCS"

authServer:
  oidc:
    roles:
      workloads:
        allowedMethods: ["*"]
        principals:
          - "spiffe:spiffe://example.org/ns/default/sa/workload"

Local Development

Prerequisites:

  • Go 1.26.2
  • Docker with Buildx
  • Docker Compose
  • task from Task
  • jq for the local integration test script

Run the ext_authz server directly:

cd cmd/oidc-gateway
CONFIG_PATH=./config.yaml \
LISTEN_ADDRESS=:9002 \
LOG_LEVEL=debug \
go run .

Run unit tests:

task test:unit

Run all linters:

task lint

Build the container image:

task build

Useful task commands:

  • task deps: install pinned local tooling into .bin.
  • task test:unit: run unit tests for all Go modules.
  • task test:unit:coverage: write unit coverage profiles under .coverage.
  • task lint:go: run golangci-lint.
  • task lint:helm: run Helm chart linting.
  • task check: run lint and license checks.
  • task build: build the oidc-gateway image with Docker Buildx.

Local Integration Tests

The repository includes a Docker Compose test environment under cmd/oidc-gateway:

  • oidc-gateway on port 9002: Envoy ext_authz gRPC server.
  • envoy on port 8080: gateway with header mutation, JWT payload forwarding, and ext_authz configured.
  • mock-backend on port 8888: echoes the identity header received from the gateway.
  • Envoy admin on port 9901.

Start the stack:

cd cmd/oidc-gateway
docker compose up --build

Run the test script in another terminal:

cd cmd/oidc-gateway
./test/test.sh

The script checks:

  • public health check access without credentials
  • rejection when credentials are missing
  • rejection of invalid JWT payloads
  • successful OIDC principal extraction
  • successful SPIFFE JWT-SVID principal extraction
  • canonical principal header forwarding to the mock backend
  • best-effort XFCC simulation for local X.509-SVID behavior

The local test setup passes x-jwt-payload directly for convenience. Production deployments should rely on Envoy jwt_authn to validate bearer JWTs and set that header. The XFCC test is a simulation; real X.509-SVID validation requires downstream mTLS and trusted SPIFFE certificate handling.

Inspect logs:

docker compose logs oidc-gateway
docker compose logs envoy
docker compose logs mock-backend
curl localhost:9901/stats | grep ext_authz

Stop the stack:

docker compose down

Environment Variables

The authorization server reads:

  • CONFIG_PATH: path to the YAML authorization config. Defaults to /etc/oidc-gateway/config.yaml.
  • LISTEN_ADDRESS: gRPC listen address. Defaults to :9002.
  • LOG_LEVEL: debug, info, warn, or error. Defaults to info.

Security Notes

  • Do not trust client-supplied principal headers or x-jwt-payload headers. Envoy configuration should strip both before authentication processing. The default principal header is x-auth-principal.
  • Keep failure_mode_allow disabled for ext_authz so authorization service failures deny protected traffic.
  • Prefer workload identity and short-lived tokens over long-lived credentials.
  • Configure issuer audiences in Envoy when your IdP supports stable audience values.
  • For SPIFFE X.509-SVID, ensure downstream client certificates are validated by Envoy through SPIFFE/SPIRE trust bundles before relying on XFCC-derived identity.
  • Keep public routes explicit and narrow.

Roadmap

See the open issues for a list of proposed features and known issues.

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are appreciated. For detailed contributing guidelines, please see CONTRIBUTING.md.

License

Distributed under the Apache-2.0 License. See LICENSE for more information.

About

Policy-based OIDC gateway for Envoy

Resources

Code of conduct

Contributing

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages