4.1 KiB
4.1 KiB
Surface.Secrets provisioning playbook (OPS-SECRETS-01)
Audience: DevOps/Ops teams shipping Scanner/Zastava/Orchestrator bundles.
Scope: how to provision secrets for the StellaOps.Scanner.Surface.Secrets providers across Kubernetes, Docker Compose, and Offline Kit.
Secret types (handles only)
- Registry pull creds (CAS / OCI / private feeds)
- CAS/attestation tokens
- TLS client certs for Surface.FS / RustFS (optional)
- Feature flag/token bundles used by Surface.Validation (non-sensitive payloads still go through handles)
All values are referenced via secret:// handles inside service configs; plaintext never enters configs or SBOMs.
Provider matrix
| Environment | Provider | Location | Notes |
|---|---|---|---|
| Kubernetes | kubernetes |
Namespace-scoped Secret objects |
Mount-free: providers read via API using service account; RBAC must allow get/list on the secret names. |
| Compose (connected) | file |
Host-mounted path (e.g., /etc/stellaops/secrets) |
Keep per-tenant subfolders; chmod 700 root; avoid embedding in images. |
| Airgap/Offline Kit | file |
Unpacked bundle surface-secrets/<tenant>/... |
Bundled as encrypted payloads; decrypt/unpack to the expected directory before first boot. |
| Tests | inline |
Environment variables or minimal inline JSON | Only for unit/system tests; disable in prod (SCANNER_SURFACE_SECRETS_ALLOW_INLINE=false). |
Kubernetes workflow
- Namespace: choose one per environment (e.g.,
stellaops-prod). - Secret layout: one K8s Secret per tenant+component to keep RBAC narrow.
apiVersion: v1
kind: Secret
metadata:
name: scanner-secrets-default
namespace: stellaops-prod
stringData:
registry.json: |
{ "type": "registry", "name": "default", "username": "svc", "password": "********", "scopes": ["stella/*"] }
cas.json: |
{ "type": "cas-token", "name": "default", "token": "********" }
- RBAC: service accounts for Scanner Worker/WebService and Zastava Observer/Webhook need
get/liston these secrets. - Values: set in Helm via
surface.secrets.provider=kubernetesandsurface.secrets.namespace=<ns>(already templated invalues*.yaml).
Compose workflow
- Create secrets directory (default
/etc/stellaops/secrets). - Layout per schema (see
docs/modules/scanner/design/surface-secrets-schema.md):
/etc/stellaops/secrets/
tenants/default/registry/default.json
tenants/default/cas/default.json
- Set env in
.envfiles:
SCANNER_SURFACE_SECRETS_PROVIDER=file
SCANNER_SURFACE_SECRETS_ROOT=/etc/stellaops/secrets
SCANNER_SURFACE_SECRETS_NAMESPACE=
SCANNER_SURFACE_SECRETS_ALLOW_INLINE=false
ZASTAVA_SURFACE_SECRETS_PROVIDER=${SCANNER_SURFACE_SECRETS_PROVIDER}
ZASTAVA_SURFACE_SECRETS_ROOT=${SCANNER_SURFACE_SECRETS_ROOT}
- Ensure docker-compose mounts the secrets path read-only to the services that need it. Use
SURFACE_SECRETS_HOST_PATHto point at the decrypted bundle on the host (defaults to./offline/surface-secretsin the Compose profiles).
Offline Kit workflow
- The offline kit already ships encrypted
surface-secretsbundles (seedocs/24_OFFLINE_KIT.md). - Operators must: (a) decrypt using the provided key, (b) place contents under
/etc/stellaops/secrets(or override*_SURFACE_SECRETS_ROOT), (c) keep permissions 700/600. - Set
*_SURFACE_SECRETS_PROVIDER=fileand root path envs as in Compose; Kubernetes provider is not available offline.
Validation & observability
- Surface.Validation will fail readiness if required secrets are missing or malformed.
- Metrics/Logs: look for
surface.secrets.*issue codes; readiness should fail onErrorseverities. - For CI smoke: run service with
SURFACE_SECRETS_ALLOW_INLINE=trueand inject test secrets via env for deterministic integration tests.
Quick checklist
- Provider selected per environment (
kubernetes/file/inline) - Secrets directory or namespace populated per schema
- RBAC (K8s) or file permissions (Compose/offline) locked down
- Env variables set for both Scanner (
SCANNER_*) and Zastava (ZASTAVA_*prefixes) - Readiness wired to Surface.Validation so missing secrets block rollout