Inspired by traefik-forward-auth by thomseddon (MIT License).
cx-traefik-forward-auth is a standalone authorization middleware for Traefik that provides OIDC authentication and/or opaque token validation (introspection) for the traefik reverse proxy. It's main goal is to work as authentication feature in API Gateway solution that Traefik provides. At the current state of implementation, the authentication is based on reading the Authorization header from the request and verifying it.
"Authorization": "Bearer <access-token>"
There are two types of verification possible:
- Introspection
- Verifying the signature (default)
In both cases the introspection endpoint or provider signature keys that are needed to verify the token are read from its discovery endpoint from OIDC_ISSUER_URL variable. Please mind two aspects:
- introspection is only possible if the client secret was provided.
- Currently decoding of symetrical (e.g. HS256) or eliptical (e.g. ES256 - TODO) keys is not supported.
In addition to that, it is possible to use this service to obtain the token (so it acts as OIDC Client). In order to that the LOGIN_WHEN_NO_TOKEN should be set to true.
WARNING! This feature is NOT meant to be used on production.
At the current state of implementation, two authentication flows are possible (both OIDC-conformant):
- Implicit Flow
- Authorization Code Flow (default)
Currently tested providers:
Provider | Version | Result | Comment |
---|---|---|---|
Keycloak | 17.1 | ✅ | |
ZITADEL | 2.64.1 | ✅ | |
SAP Commerce | ? | ✅ | |
? | ✅ | Planned | |
GitHub | ✅ |
- Prepared traefik-based infrastructure
- [OPTIONAL] ModHeader to include authorization header in browser request
Environmental variables:
Variable Name | Type | Obligatory | Comment |
---|---|---|---|
APP_NAME | string | No | Displayed service (app) name |
APP_VERSION | string | No | Displayed service (app) version |
APP_PORT | int | No | Service running port |
HOST_URI | string | Yes | URI of the host the service is running on |
ENVIRONMENT | string | Yes | 'development' or 'production' |
OIDC_ISSUER_URL | string | Yes | Main Issuer's URL - all data are retrieved from there |
OIDC_CLIENT_ID | string | Yes | OIDC client id |
OIDC_CLIENT_SECRET | string | No | OIDC client secret (if set) |
OIDC_VERIFICATION_TYPE | string | Yes | 'jwt' - decoding or 'introspection' - asking AS |
JWT_STRICT_AUDIENCE | boolean | Yes | true if token should be used for strict audinence only |
JWT_TOKEN_TYPE | string | No | Used token, either 'access_token' (default) or 'id_token' |
AUTH_ENDPOINT | string | No | Service redirection endpoint, '/_oauth' by default |
AUTH_ALLOW_UNSEC_OPTIONS | boolean | No | Allow unsecured OPTIONS request, false by default |
LOGIN_WHEN_NO_TOKEN | boolean | Yes | true if login functionality should be on (dev only!) |
LOGIN_AUTH_FLOW | string | No | 'code' (default) or 'id_token token' (implicit flow) |
LOGIN_SCOPE | string | No | Requested scope(s), defaults to "openid email profile" |
LOGIN_COOKIE_NAME | string | No | Name of the browser cookie, only if LOGIN_WHEN_NO_TOKEN=true |
LOGIN_SESSION_SECRET | string | No | Randomized secret for cookie-session |
AUTH_ROLES_STRUCT | string | No | Structure of roles (list) in token payload (dot notation) |
AUTH_ROLE_NAME | string | No | Role name to check in token roles |
Please mind that if AUTH_ALLOW_UNSEC_OPTIONS
is set to true
, then the endpoint that should
accept OPTIONS request, should provide separate rule for that and pass X-Forwarded-Method: OPTIONS
header
to cx-traefik-forward-auth there, for instance (docker).
...
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.add-options-header.headers.customrequestheaders.X-Forwarded-Method=OPTIONS"
- "traefik.http.routers.your-endpoint-options.rule=Host(`your-endpoint.com`) && Method(`OPTIONS`)"
- "traefik.http.routers.your-endpoint-options.middlewares=add-options-header,cx-traefik-forward-auth"
...
Additionally, both AUTH_ROLES_STRUCT
and AUTH_ROLE_NAME
have to be either set or empty. Object dot notation is presented below:
resource_access.dummy-client.roles
and is equall to JSON notation:
...
"resource_access": {
"dummy-client": {
"roles": [...],
},
},
Docker Standalone:
traefik:
image: traefik:latest
container_name: cx-example-traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- cx-example-net
ports:
- 80:80
- 443:443
volumes:
- /etc/localtime:/etc/localtime:ro
# - /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/traefik.toml:/etc/traefik/traefik.toml:ro
- ./traefik/services.toml:/etc/traefik/services.toml:ro
- ./traefik/acme.json:/etc/traefik/acme.json
- ./logs/traefik-access.log:/traefik-access.log
- ./logs/traefik-service.log:/traefik-service.log
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.traefik.entrypoints=web"
- "traefik.http.routers.traefik.rule=Host(`localhost`)"
- "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
- "traefik.http.routers.traefik-secure.entrypoints=websecure"
- "traefik.http.routers.traefik-secure.rule=Host(`localhost`)"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=hypercpq"
- "traefik.http.routers.traefik-secure.service=api@internal"
- "traefik.http.routers.traefik-secure.middlewares=traefik-forward-auth"
# https://doc.traefik.io/traefik/providers/docker/#docker-api-access
socket-proxy:
image: tecnativa/docker-socket-proxy
container_name: cx-example-socket-proxy
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: 1
networks:
- cx-example-net
traefik-forward-auth:
image: creoox/cx-traefik-forward-auth:1.1.5
container_name: cx-example-traefik-forward-auth
env_file:
- ./cx-traefik-forward-auth.env
networks:
- cx-example-net
labels:
- "traefik.enable=true"
- "traefik.docker.network=cx-example-net"
- "traefik.http.middlewares.traefik-forward-auth.forwardauth.address=http://traefik-forward-auth:4181"
- "traefik.http.middlewares.traefik-forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User"
- "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181"
Docker Swarm:
Not tested -> TODO
Kubernetes:
Not implemented -> TODO
- docker
- docker-compose
- [Optional & HIGHLY Recommended] GNU make (see below)
It is recommended to make use of make commands and in order to do so install GNU make
- Unix/Linux -> ready-to-go more info
- Windows (Powershell) -> install chocolatey and then run
choco install make
in Powershell - MacOS -> for most recent versions you should be ready-to-go, if not try installing it with homebrew
Mind that all below commands can be run natively using docker-compose (not recommended, see Makefile for details)
Development Environment:
make build-dev-env
make run-dev-env
make run-unit-tests
make run-ut-coverage-html
make run-lint-check
make down-dev-env