The application receives a unique PostgreSQL username and password with a limited lifetime from Stronghold. Stronghold creates the database user on request and removes it when the lease expires. The password is not stored in manifests or Secret objects.
Goal
Configure issuing dynamic PostgreSQL credentials to the myapp application running in the myapp namespace of a Deckhouse Platform cluster, and deliver them to the pod with automatic renewal.
Prerequisites
To follow this example, you need:
- Stronghold deployed in DP and a token with permissions to configure secrets engines, policies, and auth methods.
- A PostgreSQL server reachable from Stronghold pods and an account that can create roles (
CREATEROLE), for examplestronghold. - Cluster access with
d8 k.
In DP, the Kubernetes auth method for the current cluster is created automatically at the kubernetes_local path. If you use a different path, replace kubernetes_local in the commands below.
Step 1. Configure the database secrets engine
Enable the database secrets engine and connect it to PostgreSQL:
Enable the secrets engine:
d8 stronghold secrets enable databaseConfigure the PostgreSQL connection:
d8 stronghold write database/config/myapp-postgres plugin_name="postgresql-database-plugin" allowed_roles="myapp" connection_url="postgresql://{{username}}:{{password}}@postgres.db.svc:5432/myapp?sslmode=require" username="stronghold" password="<INITIAL_PASSWORD>" password_authentication="scram-sha-256"<INITIAL_PASSWORD>is the current password of thestrongholdaccount. In the next step, Stronghold replaces it with a generated one.Rotate the service account password so that only Stronghold knows it:
d8 stronghold write -force database/rotate-root/myapp-postgres
Step 2. Create a database role
The role defines the SQL statements for creating a user and the credential lifetime:
d8 stronghold write database/roles/myapp db_name="myapp-postgres" creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" revocation_statements="REVOKE ALL ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"; DROP ROLE IF EXISTS \"{{name}}\";" default_ttl="1h" max_ttl="24h"default_ttl: lease duration on issue and on each renewal.max_ttl: maximum credential lifetime; after it, the application needs new credentials.
Step 3. Create a policy
Create a policy that allows reading credentials of the myapp role:
d8 stronghold policy write myapp-db - <<'POLICY'
path "database/creds/myapp" {
capabilities = ["read"]
}
POLICYRenewing and revoking your own leases and token is allowed by the built-in default policy.
Step 4. Create a Kubernetes auth role
Create a ServiceAccount for the application and bind it to the policy through a Kubernetes auth role:
Create a namespace and a service account:
d8 k create namespace myapp d8 k -n myapp create serviceaccount myappBind the service account to the policy:
d8 stronghold write auth/kubernetes_local/role/myapp bound_service_account_names=myapp bound_service_account_namespaces=myapp policies=myapp-db ttl=1h
Step 5. Deliver credentials to the pod
For dynamic credentials, use Stronghold Agent as a sidecar: it renews the lease and re-renders the file when credentials change. Methods that inject values only at pod start (env-injector, init container) fit only if the pod restarts more often than max_ttl expires.
Agent configuration for this scenario:
stronghold {
address = "https://stronghold.example.com"
}
auto_auth {
method "kubernetes" {
mount_path = "auth/kubernetes_local"
config = {
role = "myapp"
}
}
}
template {
destination = "/secrets/db.env"
contents = <<-EOT
{{ with secret "database/creds/myapp" }}
DB_USER={{ .Data.username }}
DB_PASSWORD={{ .Data.password }}
{{ end }}
EOT
}A pod manifest with an Agent sidecar, an in-memory emptyDir volume, and a ConfigMap with the configuration is given in Delivering secrets to Kubernetes pods.
Step 6. Handle credential changes in the application
Agent renews the lease every default_ttl until max_ttl is reached. After that, Agent requests new credentials and overwrites the /secrets/db.env file. The old user is removed when its lease is revoked.
The application must either:
- re-read the file on change and reconnect to the database;
- or exit on an authentication error so that Kubernetes restarts the container with new credentials.
Choose max_ttl so that credentials change no more often than the application can reconnect correctly.
Verification
Check the result:
Request credentials manually with an administrator token:
d8 stronghold read database/creds/myappThe response must contain the
lease_id,lease_duration,username, andpasswordfields.Check that the user is created in PostgreSQL:
SELECT rolname, rolvaliduntil FROM pg_roles WHERE rolname LIKE 'v-%';Make sure the credentials file exists in the pod:
d8 k -n myapp exec deploy/myapp -c app -- cat /secrets/db.envList active leases for the role:
d8 stronghold list sys/leases/lookup/database/creds/myapp/
Cleanup
Delete the objects created in this example:
Delete test resources in the cluster:
d8 k delete namespace myapp.Revoke all credentials issued for the role:
d8 stronghold lease revoke -prefix database/creds/myapp/Delete the role, policy, and configuration:
d8 stronghold delete auth/kubernetes_local/role/myapp d8 stronghold policy delete myapp-db d8 stronghold delete database/roles/myapp d8 stronghold delete database/config/myapp-postgres