기술 자료

shdctl secret commands

Generate and deploy Kubernetes Secret manifests from a secrets.import.yaml input, and recover that input from a cluster
읽는 시간 13분최근 업데이트: 2일 전

Use the
secret
command group to render Kubernetes
Secret
manifests from your import file, apply them to your cluster, and recover that import file from a cluster.

Generate secrets

Create Kubernetes secret YAML files from your secret definitions. For the import file format, refer to the annotated example import file.
Generate the secrets from your import file without prompting:
shdctl secret generate --import secrets.import.yaml --use-defaults
Parameters:
  • --import
    : Path to the secrets import file, in the import file format. Any value in the file the release's schema does not ask for is not read, and each one is warned about, naming the file and the entry — a secret the schema does not define, a key that secret does not declare, or a key naming a field the schema derives from a default. Read those before deploying: where the schema generates the field you meant to supply, a fresh random value is generated in place of yours.
  • --use-defaults
    : Use default values or auto-generate values without prompts (non-interactive mode). If a required field has no default or auto-generate option, the tool writes
    TBD
    and logs a warning so you can update it before deployment. With
    --persist
    , a placeholder is saved like any other value, and runs that read that file back reuse it without repeating the warning: replace it in the file.
  • --output
    : Output file path (defaults to
    secrets.yaml
    ). An existing file is replaced on every run
  • --extracted-release
    : Path to the extracted release directory (defaults to
    ./extracted-release
    )
  • --name
    : Write only the secret with this name — one this release and manifest produce, or the image pull secret. The secrets derive from one another, so every generated value has to come from your input: pass
    --import
    with the file
    shdctl secret export
    rebuilds from the cluster, or your
    --persist
    file. A run that would mint one afresh is refused, naming it; so is an unknown name, listing the ones that would work, and the pull secret's name when that secret would be skipped. Nothing is written in any of these cases
  • --clean-output
    : Remove the output file before generating, so a run that fails does not leave the previous one behind (default: false). Nothing else in its directory is touched
  • --skip-version-check
    : Skip the shdctl version compatibility check against the release package
  • --persist <path>
    : Save the values of this run back to an import-format file at
    <path>
    for reuse on subsequent runs — every value you supplied or shdctl generated, not the ones derived from them. On re-runs, the persisted file is auto-loaded and existing values are reused without regeneration — but only when no
    --import
    is given: with
    --import
    , that file supplies the values and
    <path>
    is only written. The path is required (no implicit default).
Example:
shdctl secret generate --import secrets.import.yaml --use-defaults --output ./generated-secrets.yaml
Keep generated values stable across runs. Any value your import file does not carry is generated afresh on every run: a database password, an encryption key, the storage abstraction's RSA key. Deploying a regenerated value over the one a running service holds breaks that service. So once a deployment exists, generate from a file that carries every value — the one
--persist
writes, or the one
shdctl secret export
rebuilds from the cluster. When an upgrade adds a secret, pass
--persist
on the run whose output you deploy, so the file records the value the cluster gets: a later run would mint a different one. If you missed it,
shdctl secret export
rebuilds the file from the cluster.

Object store credentials

With
configuration.objectStore.provider: s3
in your manifest, the object store's credentials are yours to supply —
secret generate
prompts for them instead of generating them, because a generated key could never authenticate against your store:
Enter the access key ID for your S3-compatible object store (needs read, write and delete on every bucket named in configuration.objectStore.buckets, including studioAssets):Enter the secret access key for your S3-compatible object store (needs read, write and delete on every bucket named in configuration.objectStore.buckets, including studioAssets):
One pair has to cover every bucket named in
configuration.objectStore.buckets
— the three required ones, plus
buckets.postgresBackups
if your manifest names it. Delete is required, not just read and write, because backup retention deletes the dumps it ages out and pgBackRest deletes the backups and WAL it expires — a key without it works until the first one ages out, then fails on every retention pass. A key scoped to only some of the buckets completes this command cleanly and fails much later, the first time a backup or a transformation runs.
When the manifest names
buckets.postgresBackups
, the run also writes a second secret,
pgbackrest-s3-credentials
, holding the pgBackRest config file that carries this same pair — pgBackRest reads credentials from a config file rather than from
AWS_*
environment variables. It is derived from the pair above rather than prompted for, so it asks you for nothing extra, and it is re-derived on every run rather than being something you can supply or persist independently.
Non-interactive runs need the pair in the
--import
file, under
s3-api-storage-credentials
as
AWS_ACCESS_KEY_ID
and
AWS_SECRET_ACCESS_KEY
. A
--use-defaults
run without it leaves both at the
TBD
placeholder and only logs a warning, so check the generated file before deploying it.
On the in-cluster store, a
--persist
file carries the generated
GARAGE_*
keys rather than a pair of yours. An
s3
run does not read them — it warns that they, and
garage-rpc-secret
, are not used — and prompts for your pair instead. A persist file written by an
s3
run no longer carries the
GARAGE_*
keys, so switching back regenerates them.
On this provider the in-cluster store's
garage-rpc-secret
is not generated either — there is no in-cluster store to hand it to. With
provider: garage
(the default) nothing about this command changes.

The image pull secret

The generated file also carries the image pull secret named by
configuration.kubernetes.imagePullSecret.name
, so deploying it creates that secret too and no hand-written
kubectl create secret docker-registry
is needed. It is built from:
  • the registry
    configuration.kubernetes.docker.repository
    — which, when you pull straight from Unity's registry, is that registry;
  • the credentials you supply for
    acr-oras-credentials
    (
    ACR_USERNAME
    /
    ACR_PASSWORD
    ) in the same run. Those authenticate against the same registry, so they are the credentials the pull secret has to carry.
The credential is a point-in-time snapshot. Where the registry issues short-lived tokens — ECR's expire after 12 hours — something else has to keep the secret current; leave that owner in charge and opt out:
configuration: kubernetes: imagePullSecret: name: ecr-regcred generate: false # something else creates and refreshes it
The older spelling
imagePullSecret: ecr-regcred
— the bare secret name — is still accepted and behaves exactly as before, except that the secret is now generated for you. shdctl logs a notice pointing at the block form when it reads it; move to the block form whenever it suits you.
Nothing is generated when the manifest names no pull secret. Nothing is generated either, with the reason logged, when the manifest opts out, gives it the name of a secret the release already defines, resolves no registry, or when the registry credentials were not supplied — a
--use-defaults
run with no import file leaves them at
TBD
. shdctl never builds the pull secret from missing or placeholder credentials, so neither a pull secret already in the cluster nor another secret in this file is replaced by such a snapshot — but it cannot tell whether credentials you supply actually work.

Deploy secrets to Kubernetes

Apply the generated secrets to your Kubernetes cluster:
shdctl secret deploy
Common options:
  • --file
    ,
    -f
    : Path to the secrets YAML file (defaults to
    secrets.yaml
    )
  • --dry-run
    : Preview the kubectl command without executing it
  • --context
    : Kubernetes context to use
  • --namespace
    ,
    -n
    : passed to
    kubectl
    as
    -n
    . It cannot move the secrets: every one already names its namespace, and kubectl refuses a
    -n
    that differs. To deploy them elsewhere, change
    configuration.kubernetes.namespace
    and regenerate
It runs
kubectl apply -f <file>
, so the kubeconfig needs
get
,
create
and
patch
on secrets in the namespace.
secret generate
stamps that namespace into every secret from
configuration.kubernetes.namespace
, which is why
--namespace
is never needed.
Example:
shdctl secret deploy --file ./generated-secrets.yaml --dry-run

Recover the import file from a cluster

When the import file you deployed with is gone — it went out with an ephemeral bastion, or somebody else ran the deploy and never shared it — rebuild it from the secrets already in the cluster:
shdctl secret export --output secrets.import.yaml
The release's secrets schema drives the read: for every value the schema expects you to supply, the matching value is read out of the cluster and written back in import format. The values derived from those — connection strings and the like — are left out, because
shdctl secret generate
derives them again from the file. The result is the file
--persist
would have written — except that a value still at the
TBD
placeholder is listed below rather than copied — so you can pass it straight back to
--import
.
You need the
manifest.yaml
the deployment was generated from — it sets the namespace, and which of the conditional secrets (the object store's, for one) are read — and an extracted release for the schema (
shdctl release pull
if you no longer have one).
A namespace holding none of the secrets the schema describes — nothing deployed there yet, or the wrong namespace or context — is an error rather than an empty file: the command exits non-zero with
recovered no secret values from namespace "<namespace>"
and writes nothing. Any other failure means the cluster's secrets went unread, not that there were none, so do not generate from an empty file on the strength of it.
Common options:
  • --output
    ,
    -o
    : Output file path (defaults to
    secrets.import.yaml
    )
  • --namespace
    ,
    -n
    : Namespace to read from (defaults to
    configuration.kubernetes.namespace
    )
  • --context
    : Kubernetes context to read from (defaults to the current one)
  • --force
    : Overwrite the output file when it already exists — without it, an existing file is left alone, since it may hold values this cluster does not
  • --dry-run
    : Print the kubectl command and contact nothing
  • --extracted-release
    : Path to extracted release (defaults to
    ./extracted-release
    )
  • --skip-version-check
    : Skip the shdctl version compatibility check against the release package
Values the cluster has nothing usable for are listed at the end — a secret the deployment predates, a key that was never set, or a field still carrying the
TBD
placeholder. Only the ones you have to act on appear: a field the schema gives a default is left out, because generation resolves it from that default rather than inventing a new value.
NOT RECOVERED (2) — supply these by hand before 'shdctl secret generate',or it will generate new values and deploying those will break whateverstill uses the old ones: pixyz-license: pixyz.lic (cluster holds the TBD placeholder, so no real value is set for it) acr-oras-credentials: ACR_PASSWORD (cluster holds the TBD placeholder, so no real value is set for it)
Fill those in before generating. To confirm the recovered file reproduces what is deployed, regenerate from it and ask the cluster what applying it would change —
kubectl diff
exits 0 when nothing would:
shdctl secret generate --import secrets.import.yaml --use-defaults --output /tmp/check.yamlkubectl diff -f /tmp/check.yaml -n <namespace>
Access this needs. It reads every secret in the namespace in one collection read — Helm's release records excepted, which the API server filters out — so the kubeconfig needs the
list
verb on secrets there — not
get
. An unnamed
kubectl get secret
is a
list
request, and a Role granting only
get
fails with
secrets is forbidden: ... cannot list resource "secrets"
:
rules: - apiGroups: [""] resources: ["secrets"] verbs: ["list"]
That is more than the
get
/
create
/
patch
a deployment pipeline needs. Run it as an operator; do not widen a CI role for it. The file it writes holds your secrets in the clear (mode
0600
): keep it in your secret store, not in Git.

Import file format

An example import file for
shdctl secret generate
. Its comments say which values you supply and which ones shdctl generates:
# Example import file for shdctl secret generate# This file contains secret values organized by secret name# Each secret name maps to a dictionary of field keys and values# Values are plain text and base64-encoded for you — except the license files# (pixyz.lic, plasticd.lic), which are read as base64: encode the file's content## A value you leave out is generated only where the schema generates that field, and# then afresh on every run. A required value with no default — the registry# credentials, the licenses — is prompted for, or written as TBD under --use-defaults,# and a --persist file keeps that TBD: supply those values here, and check the# generated file for TBD before deploying it. Once a deployment exists, generate from# a file that carries every value: the one `shdctl secret generate --persist` writes,# or the one `shdctl secret export` rebuilds.## A value you supply for a generated field is checked like a generated one: passwords# are letters and digits only unless the schema allows more, novu-api's# STORE_ENCRYPTION_KEY is exactly 32 characters, and the UVCS passwords at least 18.# An RSA key you supply, such as asset-cloud-storage-abstraction's storage-key.pem,# must be a PKCS#1 key (-----BEGIN RSA PRIVATE KEY-----); convert one with# `openssl rsa -traditional -in key.pem -out key-pkcs1.pem`.## Format:# secret-name:# field-key: value# another-field: value# another-secret:# field-key: value# Credentials for the registry your cluster pulls images from# (configuration.kubernetes.docker.repository): your own registry's once you mirror# into it with `shdctl artifact sync`, or Unity's when you pull from it directly.# They make the image pull secret, and the automation manager pulls the automation# apps with them.acr-oras-credentials: ACR_USERNAME: "<your-registry-username>" ACR_PASSWORD: "<your-registry-password>"# UVCS SSL Certificate PEMcustom-ca-certificate-pem: ssl-certificate.pem: "<your-certificate-pem>"# Pixyz License (used by builtins workflow templates)pixyz-license: pixyz.lic: "<your-pixyz-license-content>"# Pixyz License for 3D Data Streamingpixyz-license-3dds: pixyz.lic: "<your-pixyz-3dds-license-content>"# Elasticsearch Secretselasticsearch-apps-credentials: password: "<your-password>"# Mini-usf Secretsmini-usf: MINIUSF_Keycloak__ClientSecret: "<your-client-secret>"# Novu API Secretsnovu-api: JWT_SECRET: "<your-jwt-secret>" STORE_ENCRYPTION_KEY: "<your-encryption-key>"# PSMDB Secrets - MongoDB/Percona Server for MongoDB secretspsmdb-secrets: MONGODB_BACKUP_PASSWORD: "<your-password>" MONGODB_CLUSTER_ADMIN_PASSWORD: "<your-password>" MONGODB_CLUSTER_MONITOR_PASSWORD: "<your-password>" MONGODB_DATABASE_ADMIN_PASSWORD: "<your-password>" MONGODB_USER_ADMIN_PASSWORD: "<your-password>"# PMM Admin Password (optional only if PMM is deployed)# pmm-admin-password:# GF_SECURITY_ADMIN_PASSWORD: "<your-password>"# PSMDB Asset Manager Passwordpsmdb-asset-manager-password: password: "<your-password>"# PostgreSQL Secretspg-db-pguser-assetsolutions: password: "<your-password>"# RabbitMQ Admin Usernamerabbitmq-asset-solutions-import-admin-user: password: "<your-password>"# S3 API Storage Credentials# Which key names apply depends on configuration.objectStore.provider in your manifest.# With the default in-cluster store (provider: garage), supply GARAGE_*; leave them out# and shdctl generates them for you.s3-api-storage-credentials: GARAGE_ACCESS_KEY: "<your-access-key>" GARAGE_SECRET_KEY: "<your-secret-key>"# With provider: s3, supply AWS_* instead — the credentials for your own object# store, able to read, write AND DELETE objects in every bucket named in# configuration.objectStore.buckets, including studioAssets, plus postgresBackups# when you set it (backup retention deletes the backups and dumps it ages out).# These cannot be generated, and GARAGE_* values are ignored on that provider.# AWS_ACCESS_KEY_ID: "<your-object-store-access-key-id>"# AWS_SECRET_ACCESS_KEY: "<your-object-store-secret-access-key>"# UVCS Licenseuvcs-license: plasticd.lic: "<your-license-content>"# UVCS configurationuvcs-configuration: UVCS_USER_PASSWORD: "<your-password>" UVCS_ADMIN_PASSWORD: "<your-password>"# Valkey Users ACLvalkey-users: users.acl.password: "<your-password>"