Skip to content

Commit 8c061a7

Browse files
committed
OSMO-6648 - Move service auth to Kubernetes Secret
Store the stable service authentication identity in an externally managed Kubernetes Secret, migrate existing PostgreSQL-backed identities safely, and wire the unified OSMO chart as the sole deployment interface.
1 parent d4b7be1 commit 8c061a7

46 files changed

Lines changed: 2012 additions & 104 deletions

Some content is hidden

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

bzl/tests/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ py_test(
8686
main = "python_image_runfiles_dedup_test.py",
8787
)
8888

89+
py_test(
90+
name = "service_auth_bootstrap_python_runfiles_dedup_test",
91+
srcs = ["python_image_runfiles_dedup_test.py"],
92+
args = [
93+
"$(rootpath //src/service/core:service_image_x86_64)",
94+
"src/service/core/service_binary.runfiles",
95+
"usr/bin/service-auth-bootstrap",
96+
"src/service/core/service_auth_bootstrap.py",
97+
],
98+
data = ["//src/service/core:service_image_x86_64"],
99+
main = "python_image_runfiles_dedup_test.py",
100+
)
101+
89102
py_test(
90103
name = "mcp_python_runfiles_dedup_test",
91104
srcs = ["python_image_runfiles_dedup_test.py"],

deployments/charts/osmo/README.md

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ It installs:
3030
- the compute backend listener and worker;
3131
- persistent CloudNativePG, Valkey, and RustFS instances;
3232
- generated development credentials, object-storage buckets, configuration,
33-
and a CPU-only default pool.
33+
and a CPU-only default pool. Service auth is generated explicitly before
34+
installation so every process uses the same identity.
3435

3536
### Prerequisites
3637

@@ -62,9 +63,22 @@ helm --kube-context kind-osmo upgrade --install cnpg cnpg/cloudnative-pg \
6263

6364
### Install OSMO
6465

65-
Install the unified chart with the single quick-start values file:
66+
Generate the shared development service-auth identity, create its Secret, then
67+
install the unified chart with the single quick-start values file:
6668

6769
```bash
70+
OSMO_SERVICE_AUTH_DIRECTORY="$(mktemp -d)"
71+
docker run --rm --user "$(id -u):$(id -g)" \
72+
--entrypoint service-auth-bootstrap \
73+
--volume "${OSMO_SERVICE_AUTH_DIRECTORY}:/output" \
74+
nvcr.io/nvidia/osmo/service:latest \
75+
generate --output /output/authentication-config.json
76+
kubectl --context kind-osmo create namespace osmo \
77+
--dry-run=client --output=yaml | kubectl --context kind-osmo apply -f -
78+
kubectl --context kind-osmo --namespace osmo create secret generic \
79+
osmo-service-auth \
80+
--from-file="authentication-config.json=${OSMO_SERVICE_AUTH_DIRECTORY}/authentication-config.json"
81+
6882
helm dependency build deployments/charts/osmo
6983
helm --kube-context kind-osmo upgrade --install osmo deployments/charts/osmo \
7084
--namespace osmo \
@@ -73,6 +87,9 @@ helm --kube-context kind-osmo upgrade --install osmo deployments/charts/osmo \
7387
--set-string compute.backendName=default \
7488
--wait \
7589
--timeout 20m
90+
91+
rm "${OSMO_SERVICE_AUTH_DIRECTORY}/authentication-config.json"
92+
rmdir "${OSMO_SERVICE_AUTH_DIRECTORY}"
7693
```
7794

7895
Inspect the release without reading generated Secret values:
@@ -197,9 +214,22 @@ kubectl --context kind-osmo --namespace cnpg-system rollout status \
197214
deployment/cnpg-cloudnative-pg --timeout=10m
198215
```
199216

200-
Install OSMO with one values file:
217+
Generate and create the shared service-auth Secret, then install OSMO with one
218+
values file:
201219

202220
```bash
221+
OSMO_SERVICE_AUTH_DIRECTORY="$(mktemp -d)"
222+
docker run --rm --user "$(id -u):$(id -g)" \
223+
--entrypoint service-auth-bootstrap \
224+
--volume "${OSMO_SERVICE_AUTH_DIRECTORY}:/output" \
225+
nvcr.io/nvidia/osmo/service:latest \
226+
generate --output /output/authentication-config.json
227+
kubectl --context kind-osmo create namespace osmo \
228+
--dry-run=client --output=yaml | kubectl --context kind-osmo apply -f -
229+
kubectl --context kind-osmo --namespace osmo create secret generic \
230+
osmo-service-auth \
231+
--from-file="authentication-config.json=${OSMO_SERVICE_AUTH_DIRECTORY}/authentication-config.json"
232+
203233
helm dependency build deployments/charts/osmo
204234
helm --kube-context kind-osmo upgrade --install osmo deployments/charts/osmo \
205235
--namespace osmo \
@@ -208,6 +238,9 @@ helm --kube-context kind-osmo upgrade --install osmo deployments/charts/osmo \
208238
--set-string compute.backendName=default \
209239
--wait \
210240
--timeout 20m
241+
242+
rm "${OSMO_SERVICE_AUTH_DIRECTORY}/authentication-config.json"
243+
rmdir "${OSMO_SERVICE_AUTH_DIRECTORY}"
211244
```
212245

213246
Check the database, workloads, storage, and Services without reading Secret
@@ -219,7 +252,7 @@ kubectl --context kind-osmo --namespace osmo wait \
219252
kubectl --context kind-osmo --namespace osmo get pods,pvc,services,jobs
220253
kubectl --context kind-osmo --namespace osmo get secret \
221254
osmo-backend-token osmo-master-encryption-key osmo-valkey-credentials \
222-
osmo-rustfs-credentials
255+
osmo-rustfs-credentials osmo-service-auth
223256
```
224257

225258
In one terminal, forward the gateway:
@@ -591,6 +624,7 @@ may reference a separate Secret. The defaults expect these keys:
591624
| `secrets.objectStorage` | `object-storage.yaml` | Workflow data, logs, and apps |
592625
| `secrets.masterEncryptionKey` | `mek.yaml` | OSMO encryption-key configuration |
593626
| `secrets.backendApiTokens.credentials[]` | `token`, optional `previous-token` | Backend authentication |
627+
| `secrets.serviceAuth` | `authentication-config.json` | Stable JWT signing identity |
594628

595629
Generated backend-token and MEK Secrets are intentionally retained because
596630
replacing either can disconnect the compute plane or make encrypted database
@@ -620,6 +654,109 @@ postgresql:
620654
name: osmo-postgresql-credentials
621655
```
622656

657+
### Service auth identity
658+
659+
The OSMO JWT signing identity is installation-scoped secret material. Every
660+
control-plane installation requires an externally persisted Kubernetes Secret
661+
containing canonical `AuthenticationConfig` JSON under
662+
`authentication-config.json`. The chart references and mounts this Secret but
663+
never renders its private key into Helm values or release state. Runtime
664+
services do not read or write `service_auth` through PostgreSQL.
665+
666+
For a fresh installation, generate the identity offline with the service image,
667+
then create the Secret before installing the chart:
668+
669+
```bash
670+
OSMO_SERVICE_AUTH_DIRECTORY="$(mktemp -d)"
671+
docker run --rm --user "$(id -u):$(id -g)" \
672+
--entrypoint service-auth-bootstrap \
673+
--volume "${OSMO_SERVICE_AUTH_DIRECTORY}:/output" \
674+
<service-image> \
675+
generate --output /output/authentication-config.json
676+
kubectl create secret generic osmo-service-auth --namespace <namespace> \
677+
--from-file="authentication-config.json=${OSMO_SERVICE_AUTH_DIRECTORY}/authentication-config.json"
678+
rm "${OSMO_SERVICE_AUTH_DIRECTORY}/authentication-config.json"
679+
rmdir "${OSMO_SERVICE_AUTH_DIRECTORY}"
680+
```
681+
682+
The generator creates the output with mode `0600`, refuses to overwrite an
683+
existing file, validates the keypair, and never writes private material to
684+
stdout. Store and back up the Secret through the installation's normal secret
685+
management system.
686+
687+
For an existing PostgreSQL-backed installation, first establish a maintenance
688+
window that prevents the old configuration API from changing `service_auth`.
689+
Delete its HPA, scale the old API deployment to zero, and verify that no old API
690+
pod remains before starting the upgrade. Replace the example release and
691+
namespace if needed.
692+
693+
```bash
694+
OSMO_RELEASE_NAME=osmo
695+
OSMO_NAMESPACE=osmo
696+
OSMO_API_SELECTOR="app.kubernetes.io/instance=${OSMO_RELEASE_NAME},app.kubernetes.io/component=api"
697+
OSMO_API_DEPLOYMENT="$(kubectl --namespace "${OSMO_NAMESPACE}" get deployment \
698+
--selector "${OSMO_API_SELECTOR}" \
699+
--output=jsonpath='{.items[0].metadata.name}')"
700+
test -n "${OSMO_API_DEPLOYMENT}"
701+
kubectl --namespace "${OSMO_NAMESPACE}" delete hpa \
702+
--selector "${OSMO_API_SELECTOR}" --ignore-not-found
703+
kubectl --namespace "${OSMO_NAMESPACE}" scale deployment \
704+
"${OSMO_API_DEPLOYMENT}" --replicas=0
705+
kubectl --namespace "${OSMO_NAMESPACE}" rollout status deployment \
706+
"${OSMO_API_DEPLOYMENT}" --timeout=5m
707+
if kubectl --namespace "${OSMO_NAMESPACE}" get pods \
708+
--selector "${OSMO_API_SELECTOR}" \
709+
--output=name | grep -q .; then
710+
echo "old API pods still exist; do not continue" >&2
711+
exit 1
712+
fi
713+
```
714+
715+
With writers stopped, pre-provision an empty Secret and authorize it for the
716+
exact Helm release:
717+
718+
```bash
719+
kubectl create secret generic osmo-service-auth \
720+
--namespace "${OSMO_NAMESPACE}"
721+
kubectl annotate secret osmo-service-auth \
722+
--namespace "${OSMO_NAMESPACE}" \
723+
"osmo.nvidia.com/service-auth-bootstrap-placeholder=${OSMO_RELEASE_NAME}"
724+
```
725+
726+
Upgrade with `secrets.serviceAuth.existingSecret.name=osmo-service-auth` and
727+
`secrets.serviceAuth.migration.enabled=true`. A pre-upgrade Job reads and
728+
decrypts the legacy DB identity, validates every public/private keypair, and
729+
writes canonical plaintext JSON into the authorized placeholder. It then reads
730+
the DB identity again and aborts if the stable authority changed during the
731+
migration. An already populated Secret is preserved only when its complete
732+
stable identity matches. Temporary hook RBAC grants only `get` and `update` on
733+
that named Secret and is removed after the hook completes.
734+
735+
The 6.4 workloads start only after the hook succeeds and read the copied
736+
identity exclusively from the mounted Secret, so existing tokens remain valid.
737+
Wait for the Secret-backed API deployment to become ready and confirm that Helm
738+
has recreated its HPA when autoscaling is enabled. After the successful upgrade,
739+
disable `migration.enabled`. Retain the legacy DB row and its MEK through the
740+
rollback window so an older binary can still use the same identity; 6.4 runtime
741+
services ignore that row.
742+
743+
```bash
744+
kubectl --namespace "${OSMO_NAMESPACE}" rollout status deployment \
745+
"${OSMO_API_DEPLOYMENT}" --timeout=10m
746+
kubectl --namespace "${OSMO_NAMESPACE}" get hpa \
747+
--selector "${OSMO_API_SELECTOR}"
748+
```
749+
750+
The migration uses `secrets.masterEncryptionKey` to decrypt legacy MEK/JWE
751+
values. Missing, malformed, changing, or mismatched identity data fails closed
752+
without generating a replacement key. `login_info` remains deployment-derived
753+
and is overlaid only in memory.
754+
755+
Intentional key rotation requires a staged keyset rollout: add the new key,
756+
roll all consumers, switch `active_key`, retain the old verification key until
757+
all tokens it signed have expired, and remove it in a later rollout. Change
758+
`rolloutNonce` on each Secret update.
759+
623760
The MEK is mounted through the typed
624761
`secrets.masterEncryptionKey.existingSecret.{name,key}` reference. Use
625762
`managementMode: external` for an operator-owned read-only Secret. Use

deployments/charts/osmo/profiles/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ values take precedence.
1111

1212
| File | Directly installable | Required environment input |
1313
| --- | --- | --- |
14-
| `quickstart.yaml` | Yes, on a development cluster | KAI Scheduler, the CloudNativePG operator, and a default dynamic StorageClass installed separately; `compute.backendName` set explicitly at install time |
15-
| `kind-self-contained.yaml` | Yes, on kind | KAI Scheduler and the CloudNativePG operator installed separately; `compute.backendName` set explicitly at install time |
14+
| `quickstart.yaml` | Yes, on a development cluster | KAI Scheduler, the CloudNativePG operator, and a default dynamic StorageClass installed separately; `osmo-service-auth` generated and created as documented; `compute.backendName` set explicitly at install time |
15+
| `kind-self-contained.yaml` | Yes, on kind | KAI Scheduler and the CloudNativePG operator installed separately; `osmo-service-auth` generated and created as documented; `compute.backendName` set explicitly at install time |
1616
| `split-plane-control.yaml` | Base overlay | PostgreSQL, Valkey, and object-storage endpoints; Kubernetes Secrets; and `externalUrl` |
1717
| `split-plane-compute.yaml` | Base overlay | A control-plane `externalUrl`, a compute authentication Secret, and `compute.backendName` set explicitly at install time |
1818

@@ -21,7 +21,8 @@ for browser, CLI, and CPU hello-world verification. It exposes the UI and API
2121
through gateway NodePort `30080` while omitting other optional services. The kind
2222
profile retains a broader local-development surface. Both profiles are
2323
development-only and intentionally use `latest` OSMO images, one replica per
24-
component, generated credentials, and embedded stateful dependencies. The split
24+
component, explicitly generated service auth, other generated credentials, and
25+
embedded stateful dependencies. The split
2526
profiles contain example names and endpoints; copy them into an environment
2627
values file before installation.
2728

deployments/charts/osmo/profiles/kind-self-contained.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ secrets:
5050
key: mek.yaml
5151
bootstrap:
5252
enabled: true
53+
serviceAuth:
54+
existingSecret:
55+
name: osmo-service-auth
56+
key: authentication-config.json
5357

5458
services:
5559
mcp:

deployments/charts/osmo/profiles/quickstart.yaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,16 @@ secrets:
4141
managedSecret:
4242
name: osmo-backend-token
4343
masterEncryptionKey:
44-
generate: true
45-
existingSecret: ''
44+
managementMode: osmo
45+
existingSecret:
46+
name: osmo-master-encryption-key
47+
key: mek.yaml
48+
bootstrap:
49+
enabled: true
50+
serviceAuth:
51+
existingSecret:
52+
name: osmo-service-auth
53+
key: authentication-config.json
4654

4755
services:
4856
ui:

deployments/charts/osmo/templates/_helpers.tpl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,33 @@ osmo.nvidia.com/mek-rollout: {{ . | quote }}
412412
{{- end }}
413413
{{- end -}}
414414

415+
{{- define "osmo.secrets.serviceAuthRolloutAnnotation" -}}
416+
osmo.nvidia.com/service-auth-rollout: {{ .Values.secrets.serviceAuth.rolloutNonce | quote }}
417+
{{- end -}}
418+
419+
{{- define "osmo.secrets.serviceAuthArgs" -}}
420+
- --service_auth_file
421+
- /etc/osmo/service-auth/authentication-config.json
422+
{{- end -}}
423+
424+
{{- define "osmo.secrets.serviceAuthVolumeMount" -}}
425+
- name: service-auth
426+
mountPath: /etc/osmo/service-auth/authentication-config.json
427+
subPath: authentication-config.json
428+
readOnly: true
429+
{{- end -}}
430+
431+
{{- define "osmo.secrets.serviceAuthVolume" -}}
432+
{{- $serviceAuth := .Values.secrets.serviceAuth -}}
433+
- name: service-auth
434+
secret:
435+
defaultMode: 292
436+
secretName: {{ required "secrets.serviceAuth.existingSecret.name is required for the control plane" $serviceAuth.existingSecret.name | quote }}
437+
items:
438+
- key: {{ required "secrets.serviceAuth.existingSecret.key is required" $serviceAuth.existingSecret.key | quote }}
439+
path: authentication-config.json
440+
{{- end -}}
441+
415442
{{- define "osmo.valkey.fullname" -}}
416443
{{- $name := default "valkey" (dig "nameOverride" "" .Values.valkey) -}}
417444
{{- $fullnameOverride := dig "fullnameOverride" "" .Values.valkey -}}

deployments/charts/osmo/templates/agent-service.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ spec:
4747
annotations:
4848
{{- include "osmo.pod.annotations" (dict "root" . "component" .Values.services.agent) | nindent 8 }}
4949
{{- include "osmo.secrets.mekRolloutAnnotation" . | nindent 8 }}
50+
{{- include "osmo.secrets.serviceAuthRolloutAnnotation" . | nindent 8 }}
5051
spec:
5152
automountServiceAccountToken: {{ .Values.services.agent.serviceAccount.automountServiceAccountToken }}
5253
securityContext:
@@ -125,6 +126,7 @@ spec:
125126
- {{ .Values.logging.logFormat | default "text" }}
126127
{{- end }}
127128
{{- include "osmo.configuration.args" . | nindent 8 }}
129+
{{- include "osmo.secrets.serviceAuthArgs" . | nindent 8 }}
128130
{{- range $arg := .Values.services.agent.extraArgs }}
129131
- {{ $arg | quote }}
130132
{{- end }}
@@ -144,6 +146,7 @@ spec:
144146
name: mek-volume
145147
readOnly: true
146148
{{- end }}
149+
{{- include "osmo.secrets.serviceAuthVolumeMount" . | nindent 8 }}
147150
{{- include "osmo.configuration.volumeMounts" . | nindent 8 }}
148151
{{- include "osmo.externalDependencies.caVolumeMounts" . | nindent 8 }}
149152
{{- include "osmo.component.extraVolumeMounts" .Values.services.agent | nindent 8 }}
@@ -183,6 +186,7 @@ spec:
183186
{{- include "osmo.pod.extraVolumes" .Values.services.agent | nindent 8 }}
184187
{{- include "osmo.gateway.upstreamTlsVolume" (dict "context" . "secretName" .Values.gateway.tls.upstreamCerts.agent) | nindent 8 }}
185188
{{- include "osmo.secrets.mekVolume" . | nindent 8 }}
189+
{{- include "osmo.secrets.serviceAuthVolume" . | nindent 8 }}
186190
{{- include "osmo.configuration.volumes" . | nindent 8 }}
187191
{{- include "osmo.externalDependencies.caVolumes" . | nindent 8 }}
188192

deployments/charts/osmo/templates/api-service.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ spec:
4848
annotations:
4949
{{- include "osmo.pod.annotations" (dict "root" . "component" .Values.services.api "protectedAnnotations" $protectedPodAnnotations) | nindent 8 }}
5050
{{- include "osmo.secrets.mekRolloutAnnotation" . | nindent 8 }}
51+
{{- include "osmo.secrets.serviceAuthRolloutAnnotation" . | nindent 8 }}
5152
spec:
5253
automountServiceAccountToken: {{ .Values.services.api.serviceAccount.automountServiceAccountToken }}
5354
securityContext:
@@ -155,6 +156,7 @@ spec:
155156
- /etc/osmo/backend-tokens
156157
{{- end }}
157158
{{- include "osmo.configuration.args" . | nindent 8 }}
159+
{{- include "osmo.secrets.serviceAuthArgs" . | nindent 8 }}
158160
{{- range $arg := .Values.services.api.extraArgs }}
159161
- {{ $arg | quote }}
160162
{{- end }}
@@ -186,6 +188,7 @@ spec:
186188
name: mek-volume
187189
readOnly: true
188190
{{- end }}
191+
{{- include "osmo.secrets.serviceAuthVolumeMount" . | nindent 8 }}
189192
{{- if .Values.secrets.backendApiTokens.enabled }}
190193
{{- range .Values.secrets.backendApiTokens.credentials }}
191194
- name: backend-token-{{ .name }}
@@ -220,6 +223,7 @@ spec:
220223
{{- include "osmo.pod.extraVolumes" .Values.services.api | nindent 8 }}
221224
{{- include "osmo.gateway.upstreamTlsVolume" (dict "context" . "secretName" .Values.gateway.tls.upstreamCerts.api) | nindent 8 }}
222225
{{- include "osmo.secrets.mekVolume" . | nindent 8 }}
226+
{{- include "osmo.secrets.serviceAuthVolume" . | nindent 8 }}
223227
{{- include "osmo.configuration.volumes" . | nindent 8 }}
224228
{{- include "osmo.externalDependencies.caVolumes" . | nindent 8 }}
225229
{{- if .Values.secrets.backendApiTokens.enabled }}

0 commit comments

Comments
 (0)