Passwordless Database Authentication (Pro)
DefectDojo Pro can sign in to a managed PostgreSQL server with a short-lived token issued to its cloud identity instead of a password. Use it when your database refuses password sign-in, or when you would rather not keep a database password in a Secret at all.
Every DefectDojo process that connects to the database does this: the web application, Celery workers, the Go API service, the orchestrator (ddorch) and its workers, the webhook gateway, and the initializer. Each one asks its cloud for a token, caches it, and sends a current one as the password whenever it opens a new connection. The server checks the token only when a connection opens, so an established connection is not affected when its token later expires.
| Cloud | Database | Setting |
|---|---|---|
| Azure | Azure Database for PostgreSQL flexible server, with Microsoft Entra authentication | azure |
| AWS | Amazon RDS or Aurora PostgreSQL, with IAM database authentication | aws |
| Google Cloud | Cloud SQL for PostgreSQL, with IAM database authentication | gcp |
Not supported together with passwordless authentication: the embedded PostgreSQL container and the optional database connection pool. The sandbox works, with an identity of its own for each sandbox role; see Sandbox.
Kubernetes (Helm)
Set database.auth.mode and leave every password setting empty:
database:
external: true
host: <server endpoint>
port: 5432
name: dojodb
user: <the application identity's database role>
password: ""
auth:
mode: azure # azure, aws or gcp
sslMode: require
serviceAccount:
annotations: {} # binds the application pods to their cloud identity (see below)
webhookGateway:
serviceAccount:
annotations: {} # the webhook gateway runs on a cloud identity of its own
database:
role: <the gateway identity's database role>The chart renders no database password, and refuses to render when database.password, database.existingSecret, postgresql.enabled or database.pool.enabled is also set, or when sandbox.enabled is set on Azure or Google Cloud without each sandbox role’s own identity (see Sandbox).
On AWS, database.host must be the instance’s own endpoint, not a DNS alias: the token is signed for the exact host name the application connects to.
In production, verify the server’s certificate: sslMode: verify-full, or verify-ca on Cloud SQL (see below). The token is the password, and require encrypts the connection without checking the server’s identity, so anything that can intercept the traffic can present its own certificate and collect a token that stays valid until it expires. A passwordless mode refuses disable, allow and prefer, which can fall back to an unencrypted connection.
Cloud SQL and Amazon RDS certificates are signed by a private certificate authority. Put that CA in a Secret and name it in database.sslRootCertSecret; the chart mounts it into every pod that connects to the database and points DD_DATABASE_SSLROOTCERT at it:
kubectl -n <namespace> create secret generic db-server-ca --from-file=ca.crt=<CA file>database:
sslMode: verify-full # verify-ca on Cloud SQL
sslRootCertSecret:
name: db-server-ca
key: ca.crtDocker Compose and other deployments
The same settings are environment variables. Leave DD_DATABASE_PASSWORD unset and put no password in DD_DATABASE_URL.
| Variable | Value |
|---|---|
DD_DATABASE_AUTH_MODE | password (the default), azure, aws or gcp |
DD_DATABASE_AUTH_AZURE_CLIENT_ID | Azure, optional: the client ID of a user-assigned managed identity |
DD_DATABASE_AUTH_AWS_REGION | AWS, optional: the region the token is signed for; defaults to AWS_REGION |
DD_DATABASE_SSLMODE | require, verify-ca or verify-full. Unset, a passwordless mode uses libpq’s PGSSLMODE if that is set, and require otherwise |
DD_DATABASE_SSLROOTCERT | Path of the CA bundle that verifies the server, for verify-full |
Each process gets its token through its cloud SDK’s default credential chain, which on a virtual machine includes the machine’s own identity: a managed identity on Azure, an instance profile on EC2, or the attached service account on Compute Engine.
Prepare the database
Do this once, as a database administrator, before the first install.
Create a database role for the application identity and one for the webhook gateway’s identity. How depends on the cloud; see the sections below.
Create both databases, owned by the application role. The orchestrator uses a second database named after the first with a
-ddorchsuffix, and an IAM role cannot create it on its own:GRANT "<app role>" TO CURRENT_USER; GRANT "<gateway role>" TO CURRENT_USER; CREATE DATABASE dojodb OWNER "<app role>"; CREATE DATABASE "dojodb-ddorch" OWNER "<app role>";Connected to
dojodb, create the extension the application needs (the application role is not an administrator) and the webhook gateway’s schema:CREATE EXTENSION IF NOT EXISTS pg_trgm; GRANT CONNECT ON DATABASE dojodb TO "<gateway role>"; CREATE SCHEMA IF NOT EXISTS whook AUTHORIZATION "<gateway role>";Remove the temporary memberships from step 2:
REVOKE "<app role>" FROM CURRENT_USER; REVOKE "<gateway role>" FROM CURRENT_USER;
Azure
Cluster: create AKS with
--enable-oidc-issuer --enable-workload-identity. Create two user-assigned managed identities, one for the application and one for the webhook gateway. Give each a federated credential for its ServiceAccount:system:serviceaccount:<namespace>:<release service account>for the application andsystem:serviceaccount:<namespace>:<release>-webhook-gatewayfor the gateway, audienceapi://AzureADTokenExchange.Annotations: annotate each ServiceAccount with
azure.workload.identity/client-id: <the identity's client ID>. DefectDojo adds theazure.workload.identity/uselabel to its pods itself.Roles: as the server’s Microsoft Entra administrator, create each identity’s role:
SELECT * FROM pgaadauth_create_principal_with_oid('<identity name>', '<principal ID>', 'service', false, false);The role is named after the identity. That name is
database.userfor the application andwebhookGateway.database.rolefor the gateway.Extension: allow-list it first by setting the server parameter
azure.extensionstoPG_TRGM.Schema: in a new database on Azure, the
publicschema belongs toazure_pg_admin. Give it to the application role in both databases:ALTER SCHEMA public OWNER TO "<app role>";TLS: the server certificate chains to a public root, so
sslMode: verify-fullwithsslRootCert: /etc/ssl/certs/ca-certificates.crtworks.
AWS
Roles: enable IAM database authentication on the instance, then create each role as a plain user:
CREATE USER <name>;. Run the steps under Prepare the database, and only then give each role IAM sign-in:GRANT rds_iam TO <name>;A role that holds
rds_iam, directly or through membership, can only sign in with IAM. If the master user is still a member of one of these roles whenrds_iamis granted, its password stops working. In that case, an IAM principal allowedrds-db:connectfor the master user can sign in with a token fromaws rds generate-db-auth-token --username <master user>and revoke the membership.IAM roles: give each identity an IAM role allowed
rds-db:connectonarn:aws:rds-db:<region>:<account>:dbuser:<DbiResourceId>/<role name>. Bind it to its ServiceAccount through the cluster’s OIDC provider (IRSA: annotate the ServiceAccount witheks.amazonaws.com/role-arn) or with EKS Pod Identity.TLS: RDS certificates chain to the Amazon RDS certificate authority, which is not in public trust stores. Download the bundle for your region from
https://truststore.pki.rds.amazonaws.com/<region>/<region>-bundle.pem, put it in thesslRootCertSecretSecret, and usesslMode: verify-full: the instance endpoint matches its certificate.Token lifetime: RDS tokens last 15 minutes. DefectDojo refreshes them before they expire.
Google Cloud
Instance: set the flag
cloudsql.iam_authentication=on.Service accounts: create one Google service account per identity with
roles/cloudsql.instanceUserandroles/cloudsql.client. Bind each to its Kubernetes ServiceAccount withroles/iam.workloadIdentityUser, and annotate the ServiceAccount withiam.gke.io/gcp-service-account: <service account email>.Database users: create each one without the
.gserviceaccount.comsuffix:gcloud sql users create <sa>@<project>.iam --instance <instance> --type cloud_iam_service_account<sa>@<project>.iamis the role name fordatabase.userandwebhookGateway.database.role.TLS: Cloud SQL server certificates come from a certificate authority of the instance’s own. Save it with
gcloud sql instances describe <instance> --format='value(serverCaCert.cert)' > ca.crt, put it in thesslRootCertSecretSecret, and usesslMode: verify-ca. That CA signs only this instance’s certificate, soverify-caalready pins the connection to it.verify-fullfails when connecting by IP address, because the certificate names the instance’s*.sql.googDNS name instead; use it only whendatabase.hostis that name and it resolves inside the cluster.
Sandbox
The sandbox keeps its data apart from production in PostgreSQL itself: it signs in as roles of its own, which cannot reach the production database, and production cannot reach the sandbox. With passwordless authentication each of its two roles (the app role and the admin role, which owns the sandbox databases) signs in with a token for a cloud identity of its own, so that separation still holds. The initializer creates the sandbox databases and checks the separation in every direction on each start, as it does with passwords.
Sandbox on Google Cloud
Create two more service accounts, for the sandbox’s app role and admin role, and give each
roles/cloudsql.instanceUserandroles/cloudsql.client.Let the application’s service account mint tokens for both. Impersonation goes through the IAM Service Account Credentials API, so enable it in the project if it is not already (
gcloud services enable iamcredentials.googleapis.com), then:gcloud iam service-accounts add-iam-policy-binding <sandbox sa email> \ --role roles/iam.serviceAccountTokenCreator \ --member serviceAccount:<application sa email>Create a Cloud SQL IAM user for each, as for the application (
gcloud sql users create <sa>@<project>.iam --type cloud_iam_service_account).As the
postgresuser, give the admin role what it needs to create and reset the sandbox databases:ALTER ROLE "<sandbox admin sa>@<project>.iam" CREATEDB; GRANT "<sandbox app sa>@<project>.iam" TO "<sandbox admin sa>@<project>.iam";Set the Helm values:
sandbox: enabled: true auth: appUser: <sandbox app sa>@<project>.iam adminUser: <sandbox admin sa>@<project>.iam appGcpServiceAccount: <sandbox app sa email> adminGcpServiceAccount: <sandbox admin sa email>
Sandbox on Azure
Create two more user-assigned managed identities, for the sandbox’s app role and admin role. Give each a federated credential for the application’s ServiceAccount (the same subject as the application’s identity), so the application pods can get tokens for them.
As the server’s Microsoft Entra administrator, create a role for each with
pgaadauth_create_principal_with_oid, as for the application, then:ALTER ROLE "<sandbox admin identity>" CREATEDB; GRANT "<sandbox app identity>" TO "<sandbox admin identity>";Set
sandbox.auth.appUserandsandbox.auth.adminUserto the identities’ names andsandbox.auth.appAzureClientIdandsandbox.auth.adminAzureClientIdto their client IDs.
Sandbox on AWS
On AWS the sandbox needs no new identities: the application’s IAM role signs a token for each sandbox role, and DefectDojo creates both roles itself.
Allow the application’s IAM role to sign in as the two sandbox roles. Their names come from the database name, so for
dojodbthey aredojodb_sandboxanddojodb_sandbox_admin. Add both to itsrds-db:connectpolicy next to the application’s own user:arn:aws:rds-db:<region>:<account>:dbuser:<DbiResourceId>/dojodb_sandbox arn:aws:rds-db:<region>:<account>:dbuser:<DbiResourceId>/dojodb_sandbox_adminAs the master user, let the application’s role create the sandbox roles and give them IAM sign-in. Do this together with the other grants in Prepare the database, before
rds_iamis granted to the application’s role:ALTER ROLE <app role> CREATEROLE CREATEDB; GRANT rds_iam TO <app role> WITH ADMIN OPTION;This replaces the plain
GRANT rds_iam TO <app role>;for the application’s role. The master user itself never becomes a member of a role that holdsrds_iam, so its password keeps working.Set
sandbox.enabled: true. Nothing goes undersandbox.authon AWS.
On the next start the initializer creates both roles without a password, grants them rds_iam and creates the sandbox databases as the admin role.
Without Helm
The same settings are environment variables: DD_SANDBOX_DATABASE_USER and DD_SANDBOX_ADMIN_DATABASE_USER for the role names, and DD_SANDBOX_DATABASE_AUTH_GCP_SERVICE_ACCOUNT / DD_SANDBOX_ADMIN_DATABASE_AUTH_GCP_SERVICE_ACCOUNT or DD_SANDBOX_DATABASE_AUTH_AZURE_CLIENT_ID / DD_SANDBOX_ADMIN_DATABASE_AUTH_AZURE_CLIENT_ID for the identities. On Azure and Google Cloud, DefectDojo refuses to start with the sandbox enabled until both roles have their own identity.
When a step needs a privilege DefectDojo’s role does not have, the initializer stops and prints the SQL for a database administrator to run.
Switching an existing install
Upgrade the install on password authentication first, so every migration is applied.
Prepare the identities and roles as above.
As an administrator, move ownership in each database:
REASSIGN OWNED BY <old password user> TO "<app role>";Switch
database.auth.modeand remove the password settings.
Troubleshooting
| Error | Cause |
|---|---|
Password returned by client is empty | The process did not get the passwordless setting. Check the pod’s DD_DATABASE_AUTH_MODE. |
The access token has invalid format (Azure), PAM authentication failed (AWS), Cloud SQL IAM service account authentication failed (Google Cloud) | The server expected a token and got something else, or the identity has no matching database role. |
pg_hba.conf rejects connection ... no encryption | TLS is off. Set sslMode to require or stricter. |
permission denied to create database from ddorch | Create the -ddorch database in advance, owned by the application role. |