Skip to main content
Version: 0.9.x

Postgres Credentials

Requirements

  • Kubernetes cluster 1.15 or newer
  • Vault 1.3.1 or newer
  • Vault Injector 0.3.0 or newer
  • Tetrate Service Bridge 0.8.0 or newer
  • Postgres 11.1 or newer

Setup Vault

Install Vault (customers are supposed to handle Vault on their side. Vault may not be installed in the Kubernetes cluster, but should be reachable from inside the Kubernetes cluster). The Vault Injector (agent-injector) have to be installed into the cluster and configured to inject sidecars. This is automatically done by the Helm chart v0.5.0+ which install Vault 0.12+ and Vault-Injector 0.3.0+. We assume here that Vault is installed in the tcc namespace.

helm install --name=vault --set='server.dev.enabled=true' ./vault-helm

Set up Database secret engine for Postgres

Enable the database secrets engine in Vault.

vault secrets enable database
Success! Enabled the database secrets engine at: database/

By default, the secrets engine is enabled at the name of the engine. To enable the secrets engine at a different path, use the -path argument.

Configure Vault with the proper plugin and connection information. Replace postgres:5432 by the full URL:port of your Postgres cluster. Only change the lower username and password with your own, don't edit the one between {{ }} in the URL, it is used as a template:

vault write database/config/tsb \
plugin_name=postgresql-database-plugin \
allowed_roles="pg-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/?sslmode=disable" \
username="<postgres-username>" \
password="<postgres-password>"

You can review the configuration by using the read action:

vault read  database/config/tcc
# Key Value
# --- -----
# allowed_roles [pg-role]
# connection_details map[connection_url:postgresql://{{username}}:{{password}}@postgres:5432/?sslmode=disable username:postgres]
# plugin_name postgresql-database-plugin
# root_credentials_rotate_statements []

Configure a role that maps a name in Vault to an SQL statement to execute to create the database credential. The max_ttl defines how long the new credentials are valid. The default_ttl defines a lease time, and the Vault-Injector will renew it until we reach the max_ttl. At this time, a new database credential will be created.

TTL values must be paired with the application's database connection life time to ensure they are all closed before the TTL expire.

Here, again, don't edit the parameters between {{ }}, they are used as a template by Vault:

vault write database/roles/pg-role \
db_name=tcc \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT ALL ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="12h" \
max_ttl="24h"
Success! Data written to: database/roles/pg-role

Again, use the read action to check the setup:

vault read  database/roles/pg-role
# Key Value
# --- -----
# creation_statements [CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";]
# db_name tcc
# default_ttl 24h
# max_ttl 24h
# renew_statements []
# revocation_statements []
# rollback_statements []

Now, generate a new credential by reading from the /creds endpoint with the name of the role. This is the mechanism that will be used by the Vault-Injector to grab credentials for your Kubernetes application:

vault read database/creds/pg-role
Key Value
--- -----
lease_id database/creds/pg-role/tUEs8eogkk9KL5erU5rLv7hD
lease_duration 24h
lease_renewable true
password A1a-1ZYMcUHKJIJH6rrc
username v-token-pg-role-KQ4ze3GYi5He0D70tEmo-1587973449

Set up Kubernetes Secret Engine

Configure a policy named “pg-auth”, This is a very non-restrictive policy, and in a production setting, we should lock this down more.

vault policy write pg-auth - <<EOF
path "database/creds/*" {
capabilities = ["read"]
}
EOF
Success! Uploaded policy: pg-auth

Configured Vault to enable access to the Kubernetes API. This example assumes that you are running commands in the Vault pod using kubectl exec. If not, you will need to find the right JWT Token, Kubernetes API URL (that Vault will use to connect to Kubernetes) and the CA certificate of the vaultserver service account:

vault auth enable kubernetes
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

Attach the database policy to service accounts from the management namespace (tcc namespace here):

vault write auth/kubernetes/role/pg \
bound_service_account_names=* \
bound_service_account_namespaces=tsb \
policies=pg-auth \
ttl=24h

If you want more restrictions, you can create one role per ServiceAccount. For Postgres, you will need one for tcc-oap SA and one for tcc-spm SA:

vault write auth/kubernetes/role/spm \
bound_service_account_names=tcc-spm \
bound_service_account_namespaces=tcc \
policies=pg-auth \
ttl=24h
vault write auth/kubernetes/role/oap \
bound_service_account_names=tcc-oap \
bound_service_account_namespaces=tcc \
policies=pg-auth \
ttl=24h

Inject secrets into the pod

To use Vault Agent Injector in combination with Postgres, add the following deployment pod annotations and environment variables to the ManagementPlane custom resource.

release 0.8.0 or newer

An overlay is used to re-configure the deployment on the fly:

spec:
components:
apiServer:
kubeSpec:
deployment:
podAnnotations:
vault.hashicorp.com/agent-inject: 'true'
vault.hashicorp.com/agent-inject-secret-config.yaml: 'database/creds/pg-role'
vault.hashicorp.com/agent-inject-template-config.yaml: |
{{- with secret "database/creds/pg-role" -}}
data:
username: {{ .Data.username }}
password: {{ .Data.password }}
{{- end -}}
vault.hashicorp.com/role: 'pg'
vault.hashicorp.com/secret-volume-path: /etc/dbvault
overlays:
- apiVersion: v1
kind: Deployment
name: tsb
patches:
- path: spec.template.spec.containers[name:tsb].args.[:/etc/db/config\.yaml]
value: /etc/dbvault/config.yaml
- path: spec.template.spec.containers[name:tsb].args.[:86400s]
value: "1h"
spmServer:
kubeSpec:
deployment:
podAnnotations:
vault.hashicorp.com/agent-inject: 'true'
vault.hashicorp.com/agent-inject-secret-config.yaml: 'database/creds/pg-role'
vault.hashicorp.com/agent-inject-template-config.yaml: |
{{- with secret "database/creds/pg-role" -}}
data:
username: {{ .Data.username }}
password: {{ .Data.password }}
{{- end -}}
vault.hashicorp.com/role: 'pg'
vault.hashicorp.com/secret-volume-path: /etc/dbvault
job:
podAnnotations:
vault.hashicorp.com/agent-inject: 'true'
vault.hashicorp.com/agent-inject-secret-config.yaml: 'database/creds/pg-role'
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/agent-inject-template-config.yaml: |
{{- with secret "database/creds/pg-role" -}}
data:
username: {{ .Data.username }}
password: {{ .Data.password }}
{{- end -}}
vault.hashicorp.com/role: 'pg'
vault.hashicorp.com/secret-volume-path: /etc/dbvault
overlays:
- apiVersion: v1
kind: Deployment
name: spm
patches:
- path: spec.template.spec.containers[name:spm].args.[:/etc/db/config\.yaml]
value: /etc/dbvault/config.yaml
- path: spec.template.spec.containers[name:spm].args.[:86400s]
value: "1h"
overlays:
- apiVersion: v1
kind: CronJob
name: spmsync
patches:
- path: spec.jobTemplate.spec.template.spec.containers[name:spmsync].args.[:/etc/db/config\.yaml]
value: /etc/dbvault/config.yaml
- path: spec.jobTemplate.spec.template.spec.containers[name:spmsync].args.[:86400s]
value: "1h"

Debugging

Check roles in Postgres

You can use the Postgres command line client psql to check the role creation inside the target database, tcc:

psql -h postgres -p 5432 -U tcc -d tcc

Once connected to the database, you can use the \du command to list all the current roles for the database:

\du

List of roles
Role name | Attributes | Member of
----------------------------------------------------+------------------------------------------------------------+-------------------------------------------------------------
rds_ad | Cannot login | {}
rds_iam | Cannot login | {}
rds_password | Cannot login | {}
rds_replication | Cannot login | {}
rds_superuser | Cannot login | {pg_monitor,pg_signal_backend,rds_replication,rds_password}
rdsadmin | Superuser, Create role, Create DB, Replication, Bypass RLS+| {}
| Password valid until infinity |
rdsrepladmin | No inheritance, Cannot login, Replication | {}
tcc | Create role, Create DB +| {rds_superuser}
| Password valid until infinity |
| | {}
v-kubernet-pg-role-5OUfsUQv3xAASWZkbECV-1589890098 | Password valid until 2020-05-20 12:08:23+00 | {}
v-kubernet-pg-role-7uiTkWgsxphogXub0qpp-1589887199 | Password valid until 2020-05-20 11:20:04+00 | {}
...

Here you can see the role tcc that was used when configuring the database inside Vault and some roles like v-kubernet-pg-role-5OUfsUQv3xAASWZkbECV-1589890098 which correspond to dynamic roles created by the Vault-Injector sidecar.

You can also list the access rights that were granted to the dynamic roles. Here is an example with the role v-kubernet-pg-role-5OUfsUQv3xAASWZkbECV-1589890098:

SELECT grantee AS user, CONCAT(table_schema, '.', table_name) AS table,
CASE
WHEN COUNT(privilege_type) = 7 THEN 'ALL'
ELSE ARRAY_TO_STRING(ARRAY_AGG(privilege_type), ', ')
END AS grants
FROM information_schema.role_table_grants
WHERE grantee='v-kubernet-pg-role-5OUfsUQv3xAASWZkbECV-1589890098'
GROUP BY table_name, table_schema, grantee;

user | table | grants
----------------------------------------------------+------------------------+--------
v-kubernet-pg-role-5OUfsUQv3xAASWZkbECV-1589890098 | public.application | ALL
v-kubernet-pg-role-5OUfsUQv3xAASWZkbECV-1589890098 | public.assignment | ALL
v-kubernet-pg-role-5OUfsUQv3xAASWZkbECV-1589890098 | public.association | ALL
...