An application in Kubernetes can get secrets from Stronghold in several ways. All of them rely on the Kubernetes auth method: a pod presents its ServiceAccount token, and Stronghold issues a token with the policies bound to the role.
Comparison
The table compares the methods by where the secret is stored and how it is updated:
| Method | How the secret reaches the pod | Stored in etcd | Secret updates | When to use |
|---|---|---|---|---|
CSI driver of the secrets-store-integration module | File in a volume mounted into the container | No | Automatically every two minutes; the application must re-read the file | The application reads secrets from files; the cluster is managed by Deckhouse Platform |
env-injector of the secrets-store-integration module | Process environment variable | No | Only on pod restart | The application reads configuration only from environment variables |
| Stronghold Agent (init container or sidecar) | File rendered from a template, or environment variables of a child process | No | The sidecar re-renders templates when the secret changes or a lease is renewed | You need templates, dynamic secrets, or lease renewal without code changes |
| External Secrets Operator (ESO) | Kubernetes Secret that the pod mounts as a volume or env vars | Yes | By refreshInterval: the Secret is updated, volume files follow with kubelet delay, env vars only after a restart | Applications and manifests already expect regular Secrets; GitOps |
| Direct API call from the application | The application requests the secret itself | No | Fully controlled by the application | The application uses an SDK and can renew tokens and leases |
Consider the following when choosing a method:
- If a secret must not be stored in etcd, choose CSI, env-injector, Stronghold Agent, or direct API calls.
- Secrets in environment variables are visible to processes that can read
/proc/<PID>/environand may end up in dumps and logs. Files in a volume are not exposed this way. - For dynamic secrets and short-lived certificates, use Stronghold Agent as a sidecar or direct API calls: only these renew leases during the pod lifetime.
Preparation: role and policy
The example below applies to all methods. It assumes that the KV version 2 secrets engine is enabled at secret, and the Kubernetes auth method at kubernetes.
Create a policy that allows reading the application secrets:
d8 stronghold policy write myapp-read - <<'POLICY' path "secret/data/myapp/*" { capabilities = ["read"] } POLICYStore the secret:
d8 stronghold kv put -mount=secret myapp/db username=app password=S3cr3tCreate a role that binds the
myappServiceAccount in themyappnamespace to the policy:d8 stronghold write auth/kubernetes/role/myapp bound_service_account_names=myapp bound_service_account_namespaces=myapp token_policies=myapp-read token_ttl=1hIn DP in
Automaticmode, the Kubernetes auth method for the current cluster is created by the module at thekubernetes_localpath: in this case, useauth/kubernetes_localinstead ofauth/kuberneteshere and in the examples below (mount_path, login URL). Theauth/kubernetespath applies to a manually enabled method.Create the ServiceAccount in the cluster:
d8 k create namespace myapp d8 k -n myapp create serviceaccount myapp
Configuring the auth method itself (auth/kubernetes/config) is described in Kubernetes auth method. In Deckhouse Platform, the stronghold module in Automatic mode connects the current cluster automatically for the secrets-store-integration module.
secrets-store-integration CSI driver
The Deckhouse Platform module secrets-store-integration mounts secrets into the pod as files using a CSI driver. The secret is not stored in a Secret object and does not reach etcd.
Example of a SecretsStoreImport and a pod that mounts it:
apiVersion: deckhouse.io/v1alpha1
kind: SecretsStoreImport
metadata:
name: myapp-db
namespace: myapp
spec:
type: CSI
role: myapp
files:
- name: db-password
source:
path: secret/data/myapp/db
key: password
---
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: myapp
spec:
serviceAccountName: myapp
containers:
- name: app
image: registry.example.com/myapp:1.0.0
volumeMounts:
- name: secrets
mountPath: /mnt/secrets
readOnly: true
volumes:
- name: secrets
csi:
driver: secrets-store.csi.deckhouse.io
volumeAttributes:
secretsStoreImport: myapp-dbThe application reads the password from /mnt/secrets/db-password. The CSI driver updates the file automatically every two minutes. To use a new secret value, the application must re-read the file.
secrets-store-integration env-injector
The env-injector from the same module substitutes secret values into environment variables when the container starts. The values exist only in process memory and are not written to the pod spec.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
secrets-store.deckhouse.io/role: myapp
spec:
serviceAccountName: myapp
containers:
- name: app
image: registry.example.com/myapp:1.0.0
env:
- name: DB_USER
value: secrets-store:secret/data/myapp/db#username
- name: DB_PASSWORD
value: secrets-store:secret/data/myapp/db#passwordTo make the application pick up a new secret value, restart the pod, for example with d8 k -n myapp rollout restart deployment myapp.
Stronghold Agent
Stronghold Agent authenticates using the Kubernetes method, fetches secrets, and renders them to files using templates. In a pod, Agent runs:
- as an init container with
exit_after_auth = true— secrets are rendered once at pod start; - as a sidecar container — Agent renews the token and leases and re-renders templates when secrets change.
Files are shared with the application through an emptyDir volume with medium: Memory, so secrets are not written to the node disk.
Example Agent configuration in a ConfigMap (the section format is described in Settings):
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-agent
namespace: myapp
data:
agent.hcl: |
stronghold {
address = "https://stronghold.example.com"
}
auto_auth {
method "kubernetes" {
mount_path = "auth/kubernetes"
config = {
role = "myapp"
}
}
}
template {
destination = "/secrets/db.env"
contents = <<-EOT
{{ with secret "secret/data/myapp/db" }}
DB_USER={{ .Data.data.username }}
DB_PASSWORD={{ .Data.data.password }}
{{ end }}
EOT
}The pod example below uses the image of the stronghold module from the Deckhouse container registry. The entrypoint of this image is stronghold, so args contains agent and -config= with the path to agent.hcl. In <STRONGHOLD_IMAGE>, specify the address of this image.
An image built with stronghold bootstrap docker has no ENTRYPOINT (for details, see Local Stronghold for development). For such an image, set command: ["stronghold", "agent", "-config=/etc/stronghold-agent/agent.hcl"] instead of args.
Example pod with Agent as a sidecar:
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: myapp
spec:
serviceAccountName: myapp
containers:
- name: stronghold-agent
image: <STRONGHOLD_IMAGE>
args: ["agent", "-config=/etc/stronghold-agent/agent.hcl"]
volumeMounts:
- name: agent-config
mountPath: /etc/stronghold-agent
- name: secrets
mountPath: /secrets
- name: app
image: registry.example.com/myapp:1.0.0
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
volumes:
- name: agent-config
configMap:
name: myapp-agent
- name: secrets
emptyDir:
medium: MemoryFor the init container variant, move the stronghold-agent container to initContainers and add exit_after_auth = true to agent.hcl.
External Secrets Operator
External Secrets Operator is a third-party operator with a vault provider. Stronghold is API-compatible with Vault, so the provider can be pointed at Stronghold.
ESO creates a regular Secret object, so the secret value is stored in etcd. Enable encryption of Secret objects in etcd using cluster tooling and restrict access to Secrets with RBAC.
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: stronghold
namespace: myapp
spec:
provider:
vault:
server: https://stronghold.example.com
path: secret
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: myapp
serviceAccountRef:
name: myapp
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: myapp-db
namespace: myapp
spec:
refreshInterval: 15m
secretStoreRef:
name: stronghold
kind: SecretStore
target:
name: myapp-db
data:
- secretKey: password
remoteRef:
key: myapp/db
property: passwordDirect API call
The application can authenticate with its ServiceAccount token and read the secret on its own. Example with curl:
SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
STRONGHOLD_TOKEN=$(curl -s --request POST --data "{\"role\": \"myapp\", \"jwt\": \"${SA_TOKEN}\"}" https://stronghold.example.com/v1/auth/kubernetes/login | jq -r '.auth.client_token')
curl -s --header "X-Vault-Token: ${STRONGHOLD_TOKEN}" https://stronghold.example.com/v1/secret/data/myapp/db | jq '.data.data'SDK examples and guidance on renewing tokens and leases are in Application clients.