Skip to content

Commit a80042d

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

48 files changed

Lines changed: 4233 additions & 277 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
@@ -12,6 +12,15 @@ adaptation:
1212
group binding, not as internal `service:*` principals
1313
- document provider-specific setup in a local `README.md`
1414

15+
Managed job OBO tests use the provider for user and controller authentication,
16+
but the workload-to-submitter binding is NeMo Platform auth state. Jobs receive
17+
`NMP_WORKLOAD_IDENTITY_TOKEN_FILE`, exchange that subject token through
18+
`/apis/auth/token`, and receive a NeMo Platform token whose top-level subject
19+
is the job submitter and whose RFC 8693 `act.sub` is the workload actor. Provider
20+
manifests may include workload-provider token grants for contract tests, but
21+
managed Docker job OBO must not depend on provider-specific fields such as
22+
`jti`.
23+
1524
Open-source providers with `mode: compose-ci` are intended for the shared auth
1625
matrix. Reference-only providers stay documented and manifest-driven but are
1726
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
@@ -3,9 +3,10 @@
33
This directory contains a local Authentik-backed NeMo Platform example. Use it
44
to validate three user-visible flows:
55

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

1011
All credentials in this example are for local development only.
1112

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

88-
In the Docker Compose runtime, Authentik issues the demo workload subject token,
89-
but it does not accept the RFC 8693 token exchange grant directly. The Docker
90-
backend refreshes the Authentik subject token file, the SDK posts that token to
91-
the NeMo auth service, and the gateway trusts the NeMo auth service JWKS for
92-
exchanged workload access tokens.
89+
In the Docker Compose runtime, Authentik authenticates users and controller
90+
service principals, but managed Docker job OBO does not depend on Authentik
91+
password-grant workload tokens or an IdP `jti` claim. The Docker backend writes
92+
a NeMo Platform-owned workload proof token into the job token file, the SDK
93+
posts that token to the NeMo Platform auth service, and the gateway trusts the
94+
NeMo Platform auth service JWKS for exchanged workload access tokens. The
95+
Authentik workload-provider password grant remains in the example manifest for
96+
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
@@ -20,9 +20,9 @@ the parent directory:
2020
- `../helm/files/blueprints` as the |product-name| blueprint source.
2121
- `../.generated` for local generated keys and certificates.
2222

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

2727
## Services
2828

@@ -39,7 +39,7 @@ The stack contains:
3939
- `authentik-redis`: Redis for Authentik.
4040
- `authentik-server` and `authentik-worker`: Authentik itself.
4141

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

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

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

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

94-
## NeMo Compose Configuration
94+
## NeMo Platform Compose Configuration
9595

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

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

107110
The Docker jobs executor mounts the `gateway-tls` volume into workload
108111
containers and sets `SSL_CERT_FILE` and `REQUESTS_CA_BUNDLE` so workload code
@@ -112,23 +115,23 @@ trusts the local gateway certificate.
112115

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

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

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

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

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

147150
```text
148151
/var/run/secrets/nemo-platform/workload/token
@@ -155,9 +158,28 @@ NMP_WORKLOAD_IDENTITY_TOKEN_FILE=/var/run/secrets/nemo-platform/workload/token
155158
```
156159

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

162184
The useful end-to-end validation is the workload job in the shared tutorial:
163185
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
@@ -239,6 +239,9 @@ static_resources:
239239
- exact: x-nmp-principal-id
240240
- exact: x-nmp-principal-email
241241
- exact: x-nmp-principal-groups
242+
- exact: x-nmp-principal-on-behalf-of
243+
- exact: x-nmp-principal-on-behalf-of-email
244+
- exact: x-nmp-principal-on-behalf-of-groups
242245
- exact: x-nmp-scopes
243246
allowed_client_headers:
244247
patterns:

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ static_resources:
245245
- exact: x-nmp-principal-id
246246
- exact: x-nmp-principal-email
247247
- exact: x-nmp-principal-groups
248+
- exact: x-nmp-principal-on-behalf-of
249+
- exact: x-nmp-principal-on-behalf-of-email
250+
- exact: x-nmp-principal-on-behalf-of-groups
248251
- exact: x-nmp-scopes
249252
allowed_client_headers:
250253
patterns:

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

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

37-
- `shared-postgresql` for the shared PostgreSQL superuser, Authentik, and NeMo
37+
- `shared-postgresql` for the shared PostgreSQL superuser, Authentik, and NeMo Platform
3838
database passwords.
3939
- `shared-postgresql-nemo` for the NeMo Platform external database password.
4040
- `nemo-platform-envoy-tls` for the demo Envoy TLS certificate and CA.
41-
- `nemo-workload-token-signing-key` for the NeMo-issued workload access token
41+
- `nemo-workload-token-signing-key` for the NeMo Platform-issued workload access token
4242
signing key.
4343

4444
The chart generates `Secret/nemo-platform-envoy-tls` during Helm rendering and
@@ -59,13 +59,14 @@ mounted file.
5959
Use `--wait --wait-for-jobs` when installing the chart so Helm only returns
6060
after the blueprint has been applied.
6161

62-
## NeMo Kubernetes Override
62+
## NeMo Platform Kubernetes Override
6363

6464
The umbrella chart passes the Kubernetes-specific NeMo Platform configuration
6565
through `nemo-platform.platformConfig` values. It also configures
6666
`nemo-platform.envoyProxy.configOverride` so the NeMo Platform chart's Envoy
6767
deployment keeps the Authentik path split and validates both Authentik-issued
68-
tokens, NeMo workload-exchange tokens, and NeMo Scoped Access Key JWTs.
68+
tokens, NeMo Platform workload-exchange tokens, and NeMo Platform Scoped
69+
Access Key JWTs.
6970

7071
Kubernetes projected service account token expiration defaults to `600` seconds
7172
in the jobs backend. Override it through the NeMo Platform chart values if you
@@ -80,23 +81,36 @@ workload pods and injects:
8081
NMP_WORKLOAD_IDENTITY_TOKEN_FILE=/var/run/secrets/nemo-platform/workload/token
8182
```
8283

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

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

97108
The workload job request should not include workload auth environment
98109
variables. The Kubernetes jobs backend owns `NMP_WORKLOAD_IDENTITY_TOKEN_FILE`;
99-
users must not set `NEMO_WORKLOAD_TOKEN` or `NEMO_WORKLOAD_TOKEN_FILE`.
110+
users must not set `NMP_PRINCIPAL`, `NEMO_WORKLOAD_TOKEN`, or
111+
`NEMO_WORKLOAD_TOKEN_FILE`. When workload OBO is enabled, the workload receives
112+
only the subject token file path and obtains its delegated NeMo Platform access
113+
token by calling `/apis/auth/token`.
100114

101115
To inspect the projected token mount for a submitted job:
102116

@@ -110,8 +124,8 @@ kubectl --context "${KUBE_CONTEXT}" -n "${NAMESPACE}" describe pod \
110124

111125
## Shared Token Signing Key
112126

113-
Workload identity token exchange requires the NeMo auth service to sign the
114-
access token it mints from a Kubernetes projected service account subject
127+
Workload identity token exchange requires the NeMo Platform auth service to
128+
sign the access token it mints from a Kubernetes projected service account subject
115129
token. The chart creates `Secret/nemo-workload-token-signing-key` by default,
116130
mounts `private-key.pem` into the NeMo Platform API pod at
117131
`/etc/nmp/workload-token/private-key.pem`, and sets
@@ -120,9 +134,9 @@ configuration is used for workload-exchange access tokens and Scoped Access Key
120134
JWTs. The matching public keys are served from `/apis/auth/jwks`.
121135

122136
The Helm-rendered Envoy config authenticates protected `/apis/` requests by
123-
calling `/apis/auth/authenticate` on the NeMo API service. It does not use Envoy
124-
`claim_to_headers` for Scoped Access Keys. This keeps future revocation and
125-
dynamic-key checks inside the auth service, where access-key records can be
137+
calling `/apis/auth/authenticate` on the NeMo Platform API service. It does not
138+
use Envoy `claim_to_headers` for Scoped Access Keys. This keeps future
139+
revocation and dynamic-key checks inside the auth service, where access-key records can be
126140
looked up before Envoy forwards trusted principal headers.
127141

128142
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
@@ -68,6 +68,7 @@ test_runtimes:
6868
- platform_access_keys
6969
- workspace_rbac
7070
- workload_job
71+
- managed_workload_job_obo
7172
- device_flow
7273
- docker_subject_token_refresh
7374
- id: authentik-kubernetes
@@ -83,5 +84,6 @@ test_runtimes:
8384
- platform_access_keys
8485
- workspace_rbac
8586
- workload_job
87+
- managed_workload_job_obo
8688
- device_flow
8789
- kubernetes_token_review

0 commit comments

Comments
 (0)