Skip to content
Open
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
9 changes: 9 additions & 0 deletions contrib/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ adaptation:
group binding, not as internal `service:*` principals
- document provider-specific setup in a local `README.md`

Managed job OBO tests use the provider for user and controller authentication,
but the workload-to-submitter binding is NeMo Platform auth state. Jobs receive
`NMP_WORKLOAD_IDENTITY_TOKEN_FILE`, exchange that subject token through
`/apis/auth/token`, and receive a NeMo Platform token whose top-level subject
is the job submitter and whose RFC 8693 `act.sub` is the workload actor. Provider
manifests may include workload-provider token grants for contract tests, but
managed Docker job OBO must not depend on provider-specific fields such as
`jti`.

Open-source providers with `mode: compose-ci` are intended for the shared auth
matrix. Reference-only providers stay documented and manifest-driven but are
excluded from the local Compose-backed matrix.
19 changes: 11 additions & 8 deletions contrib/auth/authentik/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
This directory contains a local Authentik-backed NeMo Platform example. Use it
to validate three user-visible flows:

- log in to NeMo with Authentik
- call NeMo APIs through the Authentik gateway
- run a NeMo job whose workload exchanges a real Authentik workload subject token
- log in to NeMo Platform with Authentik
- call NeMo Platform APIs through the Authentik gateway
- run a NeMo Platform job whose workload exchanges a managed workload proof token for a
delegated NeMo Platform access token

All credentials in this example are for local development only.

Expand Down Expand Up @@ -88,8 +89,10 @@ The 2-minute CLI access-token lifetime is a local demo/testing setting so token
refresh is easy to observe. Do not use it as a production default; use a longer
value such as `hours=1` outside the refresh demonstration.

In the Docker Compose runtime, Authentik issues the demo workload subject token,
but it does not accept the RFC 8693 token exchange grant directly. The Docker
backend refreshes the Authentik subject token file, the SDK posts that token to
the NeMo auth service, and the gateway trusts the NeMo auth service JWKS for
exchanged workload access tokens.
In the Docker Compose runtime, Authentik authenticates users and controller

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hehe Authentik authenticates sounds awkward

service principals, but managed Docker job OBO uses a NeMo Platform-owned
opaque workload proof token. The Docker backend writes that proof token into the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe for my docker comment above, this is only for the authentik flow, and not for the "regular" flow?

job token file, the SDK posts it to the NeMo Platform auth service, and the
gateway trusts the NeMo Platform auth service JWKS for exchanged workload access
tokens. Docker OBO does not depend on IdP `jti` claims or IdP-issued workload
subject tokens.
71 changes: 44 additions & 27 deletions contrib/auth/authentik/compose/implementation-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ the parent directory:

- `../config/platform-compose-authentik.yaml` as the NeMo Platform config.
- `../gateway/envoy.yaml` as the local gateway config.
- `../helm/files/blueprints` as the |product-name| blueprint source.
- `../helm/files/blueprints` as the NeMo Platform blueprint source.
- `../.generated` for local generated keys and certificates.

The shared tutorial does not build NeMo images for Compose. It runs
`${IMAGE_REGISTRY:-my-registry}/nmp-api:${BAKE_TAG:-local}` for both the NeMo
API service and workload jobs submitted by the tutorial.
The shared tutorial does not build NeMo Platform images for Compose. It runs
`${IMAGE_REGISTRY:-my-registry}/nmp-api:${BAKE_TAG:-local}` for both the
NeMo Platform API service and workload jobs submitted by the tutorial.

## Services

Expand All @@ -37,12 +37,12 @@ The stack contains:
- `gateway-tls-init`: a small init container that copies local TLS material into
the named `gateway-tls` volume with permissions suitable for Envoy.
- `authentik-blueprint-init`: a one-shot init container that applies the shared
|product-name| blueprint before the gateway starts.
NeMo Platform blueprint before the gateway starts.
- `authentik-postgres`: PostgreSQL for Authentik.
- `authentik-redis`: Redis for Authentik.
- `authentik-server` and `authentik-worker`: Authentik itself.

`nemo` is only on the internal network. Host and workload traffic reaches NeMo
`nemo` is only on the internal network. Host and workload traffic reaches NeMo Platform
through the `gateway` service, which also joins the workload network as
`nemo-gateway`.

Expand All @@ -59,15 +59,15 @@ 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.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.
`auth.token_signing.private_key_file` at that mounted path. The NeMo Platform
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 Platform 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
the `nemo` service mounts the same volume read-only so Python HTTP clients
inside NeMo trust the demo gateway certificate.
inside NeMo Platform trust the demo gateway certificate.

All generated keys and certificates in this example are for local development
only.
Expand All @@ -94,7 +94,7 @@ The `nemo-setup` service account and app-password in the blueprint exist solely
for automated auth-idp contract tests. They are not part of the browser login
flow or the workload identity pattern.

## NeMo Compose Configuration
## NeMo Platform Compose Configuration

`platform-compose-authentik.yaml` configures NeMo Platform for this topology:

Expand All @@ -104,8 +104,11 @@ flow or the workload identity pattern.
the `nemo` container.
- Host-side CLI login uses the port-forward-like public gateway URL
`https://127.0.0.1:18080`.
- Workload subject tokens come from Authentik's workload OIDC provider.
- Exchanged workload access tokens come from NeMo's `/apis/auth/token` endpoint.
- Authentik provides user and controller service-principal authentication. The
Docker managed-job OBO binding is stored in NeMo Platform auth delegation
state, not in Authentik.
- Exchanged workload access tokens come from NeMo Platform's `/apis/auth/token`
endpoint.

The Docker jobs executor mounts the `gateway-tls` volume into workload
containers and sets `SSL_CERT_FILE` and `REQUESTS_CA_BUNDLE` so workload code
Expand All @@ -115,23 +118,25 @@ trusts the local gateway certificate.

Envoy is the public entrypoint for the Compose example. It routes:

- NeMo paths such as `/.well-known/nemo-platform/`, `/apis/`, `/health/`,
`/status`, and `/studio/` to `nemo`.
- NeMo Platform paths such as `/.well-known/nemo-platform/`, `/apis/`,
`/health/`, `/status`, and `/studio/` to `nemo`.
- `/health/gateway/ready` to an Envoy-owned readiness check that verifies both
NeMo and Authentik through their upstream clusters.
NeMo Platform and Authentik through their upstream clusters.
- Authentik paths to `authentik-server`.

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.
protected `/apis/` requests, Envoy calls NeMo Platform's `/apis/auth/ext-authz`
endpoint with the presented bearer token. The auth service validates Authentik
OIDC tokens, NeMo Platform workload-exchange access tokens, and NeMo Platform
Scoped Access Keys, then returns trusted `X-NMP-Principal-*` and `X-NMP-Scopes`
headers for Envoy to forward upstream. The public `/apis/auth/authenticate`
endpoint remains a JSON diagnostic/API endpoint for direct callers and is not
the configured Envoy callout.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

envoy callout?


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
and time claims. It cannot check NeMo Platform'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.
Expand All @@ -145,7 +150,7 @@ job request should not include `NMP_WORKLOAD_IDENTITY_TOKEN_FILE`,
`NEMO_WORKLOAD_TOKEN`, or `NEMO_WORKLOAD_TOKEN_FILE`.

When a managed Docker workload starts, the backend creates a dedicated workload
identity volume, writes an Authentik subject token to:
identity volume and writes a NeMo Platform-owned Docker workload proof token to:

```text
/var/run/secrets/nemo-platform/workload/token
Expand All @@ -158,9 +163,21 @@ NMP_WORKLOAD_IDENTITY_TOKEN_FILE=/var/run/secrets/nemo-platform/workload/token
```

The SDK reads that file and sends an RFC 8693 token exchange request to the
NeMo auth service through the gateway. The NeMo auth service validates the
Authentik subject token, mints a NeMo-signed access token, and returns it to the
workload. The workload uses that exchanged token for normal NeMo API calls.
NeMo Platform auth service through the gateway. The Docker backend registered
an internal workload delegation row before the container started. The
NeMo Platform auth service validates the proof token, checks the matching row,
mints a NeMo Platform-signed delegated access token, and returns it to the
workload. The access token uses the captured job submitter as the top-level
subject and the Docker workload as the RFC 8693 `act.sub` actor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Docker workload" is specific to local right? should this just say "container" here and below? since in kube it won't be docker


Docker supports one proof-token mechanism in this flow. The file contains a
private opaque proof token whose secret is stored only as a hash in the
delegation row.

Docker job OBO therefore does not require Authentik to issue a workload token
and does not depend on an IdP `jti` claim. The Authentik workload-provider
configuration in the manifest is retained for direct provider-token contract
tests, not for the managed Docker job exchange loop.

The useful end-to-end validation is the workload job in the shared tutorial:
the job uses the exchanged token to call the NeMo Platform API and read the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ jobs:
additional_volume_mounts:
- volume_name: "authentik_gateway_tls"
mount_path: "/etc/nmp/gateway-tls"
workload_identity:
token_endpoint: "https://nemo-gateway:8080/application/o/token/"
username: "svc-nemo"
password_env_var: "AUTHENTIK_WORKLOAD_IDENTITY_PASSWORD"
executor_defaults:
docker:
cleanup_completed_jobs_immediately: false
Expand Down
18 changes: 17 additions & 1 deletion contrib/auth/authentik/gateway/envoy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ static_resources:
key: x-forwarded-proto
value: https
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- match:
prefix: "/apis/auth/ext-authz"
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:
Expand Down Expand Up @@ -235,13 +248,16 @@ static_resources:
uri: "http://nemo:8080"
cluster: nemo
timeout: 5s
path_prefix: "/apis/auth/authenticate"
path_prefix: "/apis/auth/ext-authz"
authorization_response:
allowed_upstream_headers:
patterns:
- exact: x-nmp-principal-id
- exact: x-nmp-principal-email
- exact: x-nmp-principal-groups
- exact: x-nmp-principal-on-behalf-of
- exact: x-nmp-principal-on-behalf-of-email
- exact: x-nmp-principal-on-behalf-of-groups
- exact: x-nmp-scopes
allowed_client_headers:
patterns:
Expand Down
18 changes: 17 additions & 1 deletion contrib/auth/authentik/helm/templates/_envoy-config.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,19 @@ static_resources:
key: x-forwarded-proto
value: https
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- match:
prefix: "/apis/auth/ext-authz"
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:
Expand Down Expand Up @@ -243,13 +256,16 @@ static_resources:
uri: {{ printf "http://%s:%v" $apiServiceName .Values.api.service.port | quote }}
cluster: nemo
timeout: 5s
path_prefix: "/apis/auth/authenticate"
path_prefix: "/apis/auth/ext-authz"
authorization_response:
allowed_upstream_headers:
patterns:
- exact: x-nmp-principal-id
- exact: x-nmp-principal-email
- exact: x-nmp-principal-groups
- exact: x-nmp-principal-on-behalf-of
- exact: x-nmp-principal-on-behalf-of-email
- exact: x-nmp-principal-on-behalf-of-groups
- exact: x-nmp-scopes
allowed_client_headers:
patterns:
Expand Down
Loading
Loading