@@ -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+
6882helm dependency build deployments/charts/osmo
6983helm --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
7895Inspect 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+
203233helm dependency build deployments/charts/osmo
204234helm --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
213246Check the database, workloads, storage, and Services without reading Secret
@@ -219,7 +252,7 @@ kubectl --context kind-osmo --namespace osmo wait \
219252kubectl --context kind-osmo --namespace osmo get pods,pvc,services,jobs
220253kubectl --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
225258In 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
595629Generated backend-token and MEK Secrets are intentionally retained because
596630replacing 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+
623760The 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
0 commit comments