Skip to content

Commit 43c4803

Browse files
committed
feat(auth): add workload delegation
Signed-off-by: Ryan S <267728323+ironcommit@users.noreply.github.com>
1 parent dbe2c5f commit 43c4803

47 files changed

Lines changed: 4229 additions & 270 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

contrib/auth/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ adaptation:
1515
group binding, not as internal `service:*` principals
1616
- document provider-specific setup in a local `README.md`
1717

18+
Managed job OBO tests use the provider for user and controller authentication,
19+
but the workload-to-submitter binding is NeMo Platform auth state. Jobs receive
20+
`NMP_WORKLOAD_IDENTITY_TOKEN_FILE`, exchange that subject token through
21+
`/apis/auth/token`, and receive a NeMo Platform token whose top-level subject
22+
is the job submitter and whose RFC 8693 `act.sub` is the workload actor. Provider
23+
manifests may include workload-provider token grants for contract tests, but
24+
managed Docker job OBO must not depend on provider-specific fields such as
25+
`jti`.
26+
1827
Open-source providers with `mode: compose-ci` are intended for the shared auth
1928
matrix. Reference-only providers stay documented and manifest-driven but are
2029
excluded from the local Compose-backed matrix.

contrib/auth/authentik/README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
This directory contains a local Authentik-backed NeMo Platform example. Use it
77
to validate three user-visible flows:
88

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

1314
All credentials in this example are for local development only.
1415

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

91-
In the Docker Compose runtime, Authentik issues the demo workload subject token,
92-
but it does not accept the RFC 8693 token exchange grant directly. The Docker
93-
backend refreshes the Authentik subject token file, the SDK posts that token to
94-
the NeMo auth service, and the gateway trusts the NeMo auth service JWKS for
95-
exchanged workload access tokens.
92+
In the Docker Compose runtime, Authentik authenticates users and controller
93+
service principals, but managed Docker job OBO does not depend on Authentik
94+
password-grant workload tokens or an IdP `jti` claim. The Docker backend writes
95+
a NeMo Platform-owned workload proof token into the job token file, the SDK
96+
posts that token to the NeMo Platform auth service, and the gateway trusts the
97+
NeMo Platform auth service JWKS for exchanged workload access tokens. The
98+
Authentik workload-provider password grant remains in the example manifest for
99+
direct provider-token contract tests.

contrib/auth/authentik/compose/implementation-details.md

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ the parent directory:
2323
- `../helm/files/blueprints` as the |product-name| blueprint source.
2424
- `../.generated` for local generated keys and certificates.
2525

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

3030
## Services
3131

@@ -42,7 +42,7 @@ The stack contains:
4242
- `authentik-redis`: Redis for Authentik.
4343
- `authentik-server` and `authentik-worker`: Authentik itself.
4444

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

@@ -59,15 +59,15 @@ share local keys:
5959
The workload-token private key is mounted into `nemo` at
6060
`/var/run/secrets/nemo-platform/workload-token-signing/private-key.pem`.
6161
`platform-compose-authentik.yaml` points
62-
`auth.token_signing.private_key_file` at that mounted path. The NeMo auth
63-
service uses the private key to sign workload-exchange access tokens and Scoped
64-
Access Key JWTs, and Envoy validates those tokens through the NeMo auth service
65-
JWKS endpoints.
62+
`auth.token_signing.private_key_file` at that mounted path. The NeMo Platform
63+
auth service uses the private key to sign workload-exchange access tokens and
64+
Scoped Access Key JWTs, and Envoy validates those tokens through the
65+
NeMo Platform auth service JWKS endpoints.
6666

6767
The gateway TLS files are copied into the `gateway-tls` named volume by
6868
`gateway-tls-init`. The `gateway` service uses that volume to serve HTTPS, and
6969
the `nemo` service mounts the same volume read-only so Python HTTP clients
70-
inside NeMo trust the demo gateway certificate.
70+
inside NeMo Platform trust the demo gateway certificate.
7171

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

97-
## NeMo Compose Configuration
97+
## NeMo Platform Compose Configuration
9898

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

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

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

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

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

124127
Before authentication, Envoy removes incoming `X-NMP-Principal-*` and
125128
`X-NMP-Scopes` headers so a client cannot spoof identity or scopes. For
126-
protected `/apis/` requests, Envoy calls NeMo's
129+
protected `/apis/` requests, Envoy calls NeMo Platform's
127130
`/apis/auth/authenticate` endpoint with the presented bearer token. The auth
128-
service validates Authentik OIDC tokens, NeMo workload-exchange access tokens,
129-
and NeMo Scoped Access Keys, then returns trusted `X-NMP-Principal-*` and
130-
`X-NMP-Scopes` headers for Envoy to forward upstream.
131+
service validates Authentik OIDC tokens, NeMo Platform workload-exchange access
132+
tokens, and NeMo Platform Scoped Access Keys, then returns trusted
133+
`X-NMP-Principal-*` and `X-NMP-Scopes` headers for Envoy to forward upstream.
131134

132135
The gateway callout is required for dynamic or revocable Scoped Access Keys
133136
because Envoy JWKS validation can only prove token signature, issuer, audience,
134-
and time claims. It cannot check NeMo's access-key lifecycle state. Compose
137+
and time claims. It cannot check NeMo Platform's access-key lifecycle state. Compose
135138
keeps `auth.access_keys.enabled=true` so Scoped Access Keys can be created and
136139
validated; Envoy performs the bearer-to-header mapping before the request
137140
reaches service middleware.
@@ -145,7 +148,7 @@ job request should not include `NMP_WORKLOAD_IDENTITY_TOKEN_FILE`,
145148
`NEMO_WORKLOAD_TOKEN`, or `NEMO_WORKLOAD_TOKEN_FILE`.
146149

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

150153
```text
151154
/var/run/secrets/nemo-platform/workload/token
@@ -158,9 +161,28 @@ NMP_WORKLOAD_IDENTITY_TOKEN_FILE=/var/run/secrets/nemo-platform/workload/token
158161
```
159162

160163
The SDK reads that file and sends an RFC 8693 token exchange request to the
161-
NeMo auth service through the gateway. The NeMo auth service validates the
162-
Authentik subject token, mints a NeMo-signed access token, and returns it to the
163-
workload. The workload uses that exchanged token for normal NeMo API calls.
164+
NeMo Platform auth service through the gateway. The Docker backend registered
165+
an internal workload delegation row before the container started. The
166+
NeMo Platform auth service validates the proof token, checks the matching row,
167+
mints a NeMo Platform-signed delegated access token, and returns it to the
168+
workload. The access token uses the captured job submitter as the top-level
169+
subject and the Docker workload as the RFC 8693 `act.sub` actor.
170+
171+
Docker supports two proof-provider modes:
172+
173+
- `jwt`: for embedded standalone deployments where the Docker jobs controller
174+
has an in-process auth-owned JWT issuer. The proof JWT uses
175+
`token_use="nemo_docker_workload_subject"` and `sub=<delegation-row-name>`.
176+
- `opaque`: for split deployments where the jobs controller calls remote auth
177+
state instead of owning signing keys. The file contains a private opaque proof
178+
token type whose secret is stored only as a hash in the delegation row.
179+
180+
The checked-in Compose example uses the default `auto` mode, which selects the
181+
opaque provider unless an embedded auth-owned JWT issuer is available. Docker
182+
job OBO therefore does not require Authentik to issue a workload token and does
183+
not depend on an IdP `jti` claim. The Authentik workload-provider password
184+
grant in the manifest is retained for direct provider-token contract tests, not
185+
for the managed Docker job exchange loop.
164186

165187
The useful end-to-end validation is the workload job in the shared tutorial:
166188
the job uses the exchanged token to call the NeMo Platform API and read the

contrib/auth/authentik/gateway/envoy.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,9 @@ static_resources:
242242
- exact: x-nmp-principal-id
243243
- exact: x-nmp-principal-email
244244
- exact: x-nmp-principal-groups
245+
- exact: x-nmp-principal-on-behalf-of
246+
- exact: x-nmp-principal-on-behalf-of-email
247+
- exact: x-nmp-principal-on-behalf-of-groups
245248
- exact: x-nmp-scopes
246249
allowed_client_headers:
247250
patterns:

contrib/auth/authentik/helm/templates/_envoy-config.tpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ static_resources:
250250
- exact: x-nmp-principal-id
251251
- exact: x-nmp-principal-email
252252
- exact: x-nmp-principal-groups
253+
- exact: x-nmp-principal-on-behalf-of
254+
- exact: x-nmp-principal-on-behalf-of-email
255+
- exact: x-nmp-principal-on-behalf-of-groups
253256
- exact: x-nmp-scopes
254257
allowed_client_headers:
255258
patterns:

contrib/auth/authentik/kubernetes/implementation-details.md

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ upgrade command.
3737
The chart creates or reuses these additional local-demo Secrets during Helm
3838
rendering:
3939

40-
- `shared-postgresql` for the shared PostgreSQL superuser, Authentik, and NeMo
40+
- `shared-postgresql` for the shared PostgreSQL superuser, Authentik, and NeMo Platform
4141
database passwords.
4242
- `shared-postgresql-nemo` for the NeMo Platform external database password.
4343
- `nemo-platform-envoy-tls` for the demo Envoy TLS certificate and CA.
44-
- `nemo-workload-token-signing-key` for the NeMo-issued workload access token
44+
- `nemo-workload-token-signing-key` for the NeMo Platform-issued workload access token
4545
signing key.
4646

4747
The chart generates `Secret/nemo-platform-envoy-tls` during Helm rendering and
@@ -62,13 +62,14 @@ mounted file.
6262
Use `--wait --wait-for-jobs` when installing the chart so Helm only returns
6363
after the blueprint has been applied.
6464

65-
## NeMo Kubernetes Override
65+
## NeMo Platform Kubernetes Override
6666

6767
The umbrella chart passes the Kubernetes-specific NeMo Platform configuration
6868
through `nemo-platform.platformConfig` values. It also configures
6969
`nemo-platform.envoyProxy.configOverride` so the NeMo Platform chart's Envoy
7070
deployment keeps the Authentik path split and validates both Authentik-issued
71-
tokens, NeMo workload-exchange tokens, and NeMo Scoped Access Key JWTs.
71+
tokens, NeMo Platform workload-exchange tokens, and NeMo Platform Scoped
72+
Access Key JWTs.
7273

7374
Kubernetes projected service account token expiration defaults to `600` seconds
7475
in the jobs backend. Override it through the NeMo Platform chart values if you
@@ -83,23 +84,36 @@ workload pods and injects:
8384
NMP_WORKLOAD_IDENTITY_TOKEN_FILE=/var/run/secrets/nemo-platform/workload/token
8485
```
8586

86-
The SDK reads that file and sends an RFC 8693 token exchange request to the NeMo
87-
auth service over HTTPS. The chart mounts `ca.crt` from
87+
The SDK reads that file and sends an RFC 8693 token exchange request to the
88+
NeMo Platform auth service over HTTPS. The chart mounts `ca.crt` from
8889
`Secret/nemo-platform-envoy-tls` into Kubernetes workload pods and sets
8990
`SSL_CERT_FILE` and `REQUESTS_CA_BUNDLE` so in-pod Python HTTP clients verify
9091
the demo Envoy certificate. Host-side `nemo` commands should use
9192
`NMP_CLIENT_SSL_CERT_FILE` instead so unrelated tools keep their normal trust
9293
store.
9394

94-
The NeMo auth service validates projected service account tokens with the
95-
TokenReview API and returns a NeMo-signed JWT trusted by the NeMo Platform
96-
Envoy. The useful end-to-end validation is the workload job in the tutorial:
97-
the job pod uses the exchanged token to call the NeMo Platform API and read the
98-
workspace.
95+
The NeMo Platform auth service validates projected service account tokens with
96+
the TokenReview API and returns a NeMo Platform-signed JWT trusted by the
97+
NeMo Platform Envoy. The TokenReview response must include exactly one
98+
`authentication.kubernetes.io/pod-uid` value. The jobs controller observes the
99+
created Pod, registers an internal delegation row keyed by that Pod UID,
100+
service account subject, and audience, and auth later looks up that row from
101+
the verified TokenReview metadata. The useful end-to-end validation is the
102+
workload job in the tutorial: the job pod uses the exchanged token to call the
103+
NeMo Platform API and read the workspace.
104+
105+
Auth only needs RBAC to create TokenReview requests. It does not need to read
106+
Pods or Jobs for token exchange, and the chart grants no Pod or Job read
107+
permissions to the auth/API service account for this path. Kubernetes labels,
108+
annotations, and owner references help the jobs controller reconcile and clean
109+
up backend resources, but they are not token-exchange authorization inputs.
99110

100111
The workload job request should not include workload auth environment
101112
variables. The Kubernetes jobs backend owns `NMP_WORKLOAD_IDENTITY_TOKEN_FILE`;
102-
users must not set `NEMO_WORKLOAD_TOKEN` or `NEMO_WORKLOAD_TOKEN_FILE`.
113+
users must not set `NMP_PRINCIPAL`, `NEMO_WORKLOAD_TOKEN`, or
114+
`NEMO_WORKLOAD_TOKEN_FILE`. When workload OBO is enabled, the workload receives
115+
only the subject token file path and obtains its delegated NeMo Platform access
116+
token by calling `/apis/auth/token`.
103117

104118
To inspect the projected token mount for a submitted job:
105119

@@ -113,8 +127,8 @@ kubectl --context "${KUBE_CONTEXT}" -n "${NAMESPACE}" describe pod \
113127

114128
## Shared Token Signing Key
115129

116-
Workload identity token exchange requires the NeMo auth service to sign the
117-
access token it mints from a Kubernetes projected service account subject
130+
Workload identity token exchange requires the NeMo Platform auth service to
131+
sign the access token it mints from a Kubernetes projected service account subject
118132
token. The chart creates `Secret/nemo-workload-token-signing-key` by default,
119133
mounts `private-key.pem` into the NeMo Platform API pod at
120134
`/etc/nmp/workload-token/private-key.pem`, and sets
@@ -123,9 +137,9 @@ configuration is used for workload-exchange access tokens and Scoped Access Key
123137
JWTs. The matching public keys are served from `/apis/auth/jwks`.
124138

125139
The Helm-rendered Envoy config authenticates protected `/apis/` requests by
126-
calling `/apis/auth/authenticate` on the NeMo API service. It does not use Envoy
127-
`claim_to_headers` for Scoped Access Keys. This keeps future revocation and
128-
dynamic-key checks inside the auth service, where access-key records can be
140+
calling `/apis/auth/authenticate` on the NeMo Platform API service. It does not
141+
use Envoy `claim_to_headers` for Scoped Access Keys. This keeps future
142+
revocation and dynamic-key checks inside the auth service, where access-key records can be
129143
looked up before Envoy forwards trusted principal headers.
130144

131145
Scoped Access Keys remain disabled in the checked-in chart values by default.

contrib/auth/authentik/manifest.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ test_runtimes:
7171
- platform_access_keys
7272
- workspace_rbac
7373
- workload_job
74+
- managed_workload_job_obo
7475
- device_flow
7576
- docker_subject_token_refresh
7677
- id: authentik-kubernetes
@@ -86,5 +87,6 @@ test_runtimes:
8687
- platform_access_keys
8788
- workspace_rbac
8889
- workload_job
90+
- managed_workload_job_obo
8991
- device_flow
9092
- kubernetes_token_review

0 commit comments

Comments
 (0)