Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contrib/auth/authentik/compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ services:
NMP_CONFIG_FILE_PATH: /etc/nmp/config.yaml
NMP_CONFIG_WARNINGS_DISABLED: "1"
NMP_AUTH_POLICY_DECISION_POINT_BASE_URL: http://127.0.0.1:8080
NMP_AUTH_TOKEN_SIGNING__PRIVATE_KEY_FILE: /var/run/secrets/nemo-platform/workload-token-signing/private-key.pem
NMP_SEED_ON_STARTUP: "true"
NMP_PLATFORM_SEED_MODEL_PROVIDER_ENABLED: "false"
NMP_SECRETS_ALLOW_KEY_CREATION: "1"
Expand Down
32 changes: 20 additions & 12 deletions contrib/auth/authentik/compose/implementation-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ share local keys:
The workload-token private key is mounted into `nemo` at
`/var/run/secrets/nemo-platform/workload-token-signing/private-key.pem`.
`platform-compose-authentik.yaml` points
`auth.oidc.workload_token_private_key_file` at that mounted path. The NeMo auth
service uses the private key to sign workload-exchange access tokens, and Envoy
validates those exchanged tokens through the NeMo auth service JWKS endpoint.
`auth.token_signing.private_key_file` at that mounted path. The NeMo auth
service uses the private key to sign workload-exchange access tokens and Scoped
Access Key JWTs, and Envoy validates those tokens through the NeMo auth service
JWKS endpoints.

The gateway TLS files are copied into the `gateway-tls` named volume by
`gateway-tls-init`. The `gateway` service uses that volume to serve HTTPS, and
Expand Down Expand Up @@ -117,15 +118,22 @@ Envoy is the public entrypoint for the Compose example. It routes:
NeMo and Authentik through their upstream clusters.
- Authentik paths to `authentik-server`.

Before JWT validation, Envoy removes incoming `X-NMP-Principal-*` headers so a
client cannot spoof identity headers. For `/apis/` requests, Envoy accepts
either:

- Authentik-issued tokens from the demo providers.
- NeMo-issued workload-exchange tokens from `/apis/auth/token`.

Envoy copies the validated `sub` and `groups` claims into NeMo's principal
headers. NeMo then applies its normal workspace authorization checks.
Before authentication, Envoy removes incoming `X-NMP-Principal-*` and
`X-NMP-Scopes` headers so a client cannot spoof identity or scopes. For
protected `/apis/` requests, Envoy calls NeMo's
`/apis/auth/authenticate` endpoint with the presented bearer token. The auth
service validates Authentik OIDC tokens, NeMo workload-exchange access tokens,
and NeMo Scoped Access Keys, then returns trusted `X-NMP-Principal-*` and
`X-NMP-Scopes` headers for Envoy to forward upstream.

The gateway callout is required for dynamic or revocable Scoped Access Keys
because Envoy JWKS validation can only prove token signature, issuer, audience,
and time claims. It cannot check NeMo's access-key lifecycle state. Compose
keeps `auth.access_keys.enabled=true` so Scoped Access Keys can be created and
validated; Envoy performs the bearer-to-header mapping before the request
reaches service middleware.
Scoped Access Keys are enabled in the checked-in Compose config because the Compose
test runtime advertises the `platform_access_keys` capability.

## Workload Token Exchange

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ auth:
policy_data_refresh_interval: 2
bundle_cache_seconds: 15
admin_email: "admin@example.com"
token_signing:
issuer: "https://nemo-gateway:8080/apis/auth"
key_id: "nemo-platform-signing"
private_key_file: "/var/run/secrets/nemo-platform/workload-token-signing/private-key.pem"
access_keys:
enabled: true
oidc:
enabled: true
issuer: "http://authentik-server:9000/application/o/nemo-cli/"
Expand All @@ -26,9 +32,7 @@ auth:
workload_client_id: "nemo-platform-workload"
workload_audience: "nemo-platform"
workload_scope: "openid email groups"
workload_token_issuer: "https://nemo-gateway:8080/apis/auth"
workload_token_endpoint: "https://nemo-gateway:8080/apis/auth/token"
workload_token_private_key_file: "/var/run/secrets/nemo-platform/workload-token-signing/private-key.pem"
workload_subject_jwks_uri: "http://authentik-server:9000/application/o/nemo-workload/jwks/"
workload_subject_issuers:
- "http://authentik-server:9000/application/o/nemo-workload/"
Expand Down
152 changes: 101 additions & 51 deletions contrib/auth/authentik/gateway/envoy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,62 @@ static_resources:
prefix: "/.well-known/nemo-platform/"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
value: https
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- match:
path: "/apis/auth/discovery"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
value: https
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- match:
path: "/apis/auth/authenticate"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
value: https
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- match:
path: "/apis/auth/jwks"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
value: https
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- match:
path: "/apis/auth/token"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
Expand All @@ -51,10 +107,18 @@ static_resources:
status: 503
body:
inline_string: '{"status":"not_ready"}'
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
prefix: "/health/"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
Expand All @@ -64,6 +128,10 @@ static_resources:
path: "/status"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
Expand All @@ -73,6 +141,10 @@ static_resources:
prefix: "/studio/"
route:
cluster: nemo
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
Expand All @@ -82,6 +154,10 @@ static_resources:
prefix: "/"
route:
cluster: authentik
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
request_headers_to_add:
- header:
key: x-forwarded-proto
Expand Down Expand Up @@ -125,6 +201,8 @@ static_resources:
headers:remove("x-nmp-principal-on-behalf-of")
headers:remove("x-nmp-principal-on-behalf-of-email")
headers:remove("x-nmp-principal-on-behalf-of-groups")
headers:remove("x-nmp-authorized")
headers:remove("x-nmp-scopes")

if headers:get(":path") ~= "/health/gateway/ready" then
return
Expand All @@ -142,58 +220,30 @@ static_resources:
string.format('{"status":"not_ready","nemo":"%s","authentik":"%s"}', nemo_status, authentik_status)
)
end
- name: envoy.filters.http.jwt_authn
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
authentik_workload:
audiences:
- "nemo-platform"
- "nemo-platform-cli"
- "nemo-platform-workload"
remote_jwks:
http_uri:
uri: "http://authentik-server:9000/application/o/nemo/jwks/"
cluster: authentik
timeout: 5s
cache_duration: 600s
claim_to_headers:
- header_name: "X-NMP-Principal-Id"
claim_name: "sub"
- header_name: "X-NMP-Principal-Groups"
claim_name: "groups"
workload_exchange:
audiences:
- "nemo-platform"
remote_jwks:
http_uri:
uri: "http://nemo:8080/apis/auth/jwks"
cluster: nemo
timeout: 5s
cache_duration: 600s
claim_to_headers:
- header_name: "X-NMP-Principal-Id"
claim_name: "sub"
- header_name: "X-NMP-Principal-Groups"
claim_name: "groups"
rules:
- match:
path: "/apis/auth/discovery"
- match:
path: "/apis/auth/jwks"
- match:
path: "/apis/auth/token"
- match:
prefix: "/health/"
- match:
path: "/status"
- match:
prefix: "/apis/"
requires:
requires_any:
requirements:
- provider_name: "authentik_workload"
- provider_name: "workload_exchange"
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
failure_mode_allow: false
status_on_error:
code: ServiceUnavailable
http_service:
server_uri:
uri: "http://nemo:8080"
cluster: nemo
timeout: 5s
path_prefix: "/apis/auth/authenticate"
authorization_response:
allowed_upstream_headers:
patterns:
- exact: x-nmp-principal-id
- exact: x-nmp-principal-email
- exact: x-nmp-principal-groups
- exact: x-nmp-scopes
allowed_client_headers:
patterns:
- exact: content-type
- exact: www-authenticate
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
Expand Down
Loading
Loading