Vault how-to guides#
Practical tasks for managing secrets with Vault.
If you manage a cluster and need to register it with a Vault instance, see Onboard a Kubernetes cluster into Vault.
Set up auto-rotating secrets#
When a secret leaks (e.g., a Slack webhook committed to a public repository), you need to rotate it quickly. VSO can automatically synchronise the new secret and restart affected workloads.
1. Update the secret in Vault#
Generate a new secret value and update it in Vault. See Vault for login instructions.
2. Configure refresh interval#
Set refreshAfter to control how frequently VSO checks for updates:
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: slack-webhook
spec:
type: kv-v2
mount: <engine>
path: <path/to/secret>
refreshAfter: 60s
destination:
name: slack-webhook
create: true
overwrite: true
transformation:
excludeRaw: true
includes:
- webhook
VSO checks Vault at least every 60 seconds.
3. Add rollout restart target#
Even after the Kubernetes secret updates, running pods still have the old value. Add rolloutRestartTargets to automatically restart workloads:
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: slack-webhook
spec:
type: kv-v2
mount: <engine>
path: <path/to/secret>
refreshAfter: 60s
rolloutRestartTargets:
- kind: Deployment
name: slack-notifier
destination:
name: slack-webhook
create: true
overwrite: true
transformation:
excludeRaw: true
includes:
- webhook
Supported target kinds: Deployment, StatefulSet, DaemonSet.
Warning
Use automatic rollouts carefully — they can affect application stability during secret rotations.
Retrieve Vault secrets in a GitLab job#
GitLab CI/CD authenticates to Vault with OIDC ID tokens — no service tokens or passwords needed. By default, GitLab has read access to the dev and kv engines.
1. Create a secret with the CLI#
export VAULT_ADDR=https://vault.skao.int
export VAULT_TOKEN=$(vault login -address=${VAULT_ADDR} -field=token -no-store -method=oidc role=developer)
# Confirm the token
vault token lookup
# Write test data (replace <team> with your GitLab team slug).
# file=- reads the file's contents from standard input.
printf 'a: 1\nb: 2\n' | vault kv put -mount=dev <team>/vault-tutorial env="a secret value" file=-
2. Inject a secret as an environment variable#
vault-secret-env:
stage: test-vault
id_tokens:
VAULT_ID_TOKEN:
aud: https://gitlab.com
secrets:
TEST:
vault: <team>/vault-tutorial/env@dev
file: false
script:
- echo "Secret is '$TEST'"
3. Inject a secret as a file#
vault-secret-file:
stage: test-vault
variables:
SOME_VAR: file
id_tokens:
VAULT_ID_TOKEN:
aud: https://gitlab.com
secrets:
TEST:
vault: <team>/vault-tutorial/${SOME_VAR}@dev
script:
- cat $TEST
With file: false the variable holds the secret value; by default (file) it holds the path to the secret file. To supply whole values.yml files this way, see the next section.
Supply Helm values from Vault in GitLab CI/CD#
Replace Makefile switches and GitLab CI variables with Vault-stored values files for cleaner, more auditable deployments.
Why use Vault for Helm values?#
Traditional approach using Makefile switches:
helm upgrade --install test \
--set global.db.password=$DB_PASSWORD \
--set global.api.token=$API_TOKEN \
./charts/my-app-umbrella/
Problems: Values are hard to trace, logic scatters across the Makefile, poor auditability.
Vault approach:
helm upgrade --install test \
-f <(envsubst < /path/to/ENVIRONMENT_VALUES) \
-f <(envsubst < /path/to/DEP_STRATEGY_VALUES) \
-f <(envsubst < /path/to/APP_VALUES) \
./charts/my-app-umbrella/
Benefits: Clear precedence, single source of truth, full audit trail.
1. Structure your values in Vault#
Split configurations by purpose:
Environment-specific values (<datacentre>/<environment>/default/values.yml@dev):
global:
db:
password: <the-database-password>
Application-specific values (skao-team-system/my-app/values.yml@dev):
global:
api:
token: <the-application-api-token>
Deployment strategy values (shared/default/operator/values.yml@dev):
global:
operator: true
2. Configure GitLab CI/CD job#
k8s-test:
variables:
KUBE_NAMESPACE: 'ci-$CI_PROJECT_NAME-$CI_COMMIT_SHORT_SHA'
K8S_VALUES_FILES: "${ENVIRONMENT_VALUES} ${DEP_STRATEGY_VALUES} ${APP_VALUES}"
id_tokens:
VAULT_ID_TOKEN:
aud: https://gitlab.com
secrets:
ENVIRONMENT_VALUES:
vault: ${CLUSTER_DATACENTRE}/${CLUSTER_ENVIRONMENT}/default/values.yml@dev
file: true
DEP_STRATEGY_VALUES:
vault: shared/default/operator/values.yml@dev
file: true
APP_VALUES:
vault: skao-team-system/my-app/values.yml@dev
file: true
file: true injects secrets as files rather than environment variables.
3. Update Makefile#
Replace switch-based parameters with values files:
ifneq ($(K8S_VALUES_FILES),)
K8S_CHART_PARAMS ?= $(foreach f,$(K8S_VALUES_FILES),-f <(envsubst < $(f)))
endif
envsubst substitutes environment variables in the values files.
4. Add contextual values (optional)#
Include deployment context for traceability:
global:
context:
gitlab:
author: ${CI_COMMIT_AUTHOR}
ref: ${CI_COMMIT_REF_NAME}
commit: ${CI_COMMIT_SHA}
pipelineId: ${CI_PIPELINE_ID}
projectId: ${CI_PROJECT_ID}
project: ${CI_PROJECT_PATH}
kubernetes:
datacentre: ${CLUSTER_DATACENTRE}
environment: ${CLUSTER_ENVIRONMENT}
namespace: ${KUBE_NAMESPACE}
5. Reuse deployed values in tests#
After deploying with values files, retrieve the actual values for use in tests:
RELEASE_VALUES_FILE ?= $(RELEASE_NAME).$(KUBE_NAMESPACE).values.yml
ifneq ($(K8S_VALUES_FILES),)
K8S_CHART_PARAMS ?= $(foreach f,$(K8S_VALUES_FILES),-f <(envsubst < $(f)))
endif
k8s-post-install-chart:
@helm get values $(RELEASE_NAME) -n $(KUBE_NAMESPACE) -o json > $(RELEASE_VALUES_FILE)
Test jobs then use the exact values from the deployment.
Inject secrets through a chart’s extra resources#
Some SKAO charts expose an extraDeploy list that renders arbitrary manifests as additional resources, templated with the chart’s context. This gives you an open-ended way to add a VaultStaticSecret (or any other resource) to a deployment without changing the chart — useful when the manifest needs to be dynamic.
The mechanism is two templates working together (example from ska-ser-bar-backend):
templates/extra-list.yaml ranges over
.Values.extraDeployand renders each entry.templates/_helpers/_common.tpl defines the
renderhelper that appliestplto each entry with the full chart context.
To sync a Vault secret, add a VaultStaticSecret to extraDeploy in your values:
extraDeploy:
- apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: my-app-secret
spec:
type: kv-v2
mount: dev
path: my-team/my-app
refreshAfter: 60s
destination:
name: my-app-secret
create: true
Because each entry passes through tpl, you can template values dynamically — for example, deriving the secret name and path from the release. Provide the entry as a string block so the expressions survive into rendering:
extraDeploy:
- |
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: {{ .Release.Name }}-secret
spec:
type: kv-v2
mount: dev
path: my-team/{{ .Release.Name }}
refreshAfter: 60s
destination:
name: {{ .Release.Name }}-secret
create: true
Add a secret to Vault#
Log in to Vault with GitLab SSO
Navigate to your team’s KV engine (
dev/<team-slug>/)Create a subdirectory for your application
Add key-value pairs for your secrets
Create a
VaultStaticSecretresource to sync to Kubernetes
Note
Secrets must be in subdirectories — you cannot create secrets at the root of your user or team path.
Request team access to Vault#
Your team needs a corresponding GitLab Group to access team-specific Vault paths.
If your team doesn’t have one, request access on the System Team Support Desk.