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.

CloudDatabaseSetting
AzureAzure Database for PostgreSQL flexible server, with Microsoft Entra authenticationazure
AWSAmazon RDS or Aurora PostgreSQL, with IAM database authenticationaws
Google CloudCloud SQL for PostgreSQL, with IAM database authenticationgcp

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.crt

Docker Compose and other deployments

The same settings are environment variables. Leave DD_DATABASE_PASSWORD unset and put no password in DD_DATABASE_URL.

VariableValue
DD_DATABASE_AUTH_MODEpassword (the default), azure, aws or gcp
DD_DATABASE_AUTH_AZURE_CLIENT_IDAzure, optional: the client ID of a user-assigned managed identity
DD_DATABASE_AUTH_AWS_REGIONAWS, optional: the region the token is signed for; defaults to AWS_REGION
DD_DATABASE_SSLMODErequire, verify-ca or verify-full. Unset, a passwordless mode uses libpq’s PGSSLMODE if that is set, and require otherwise
DD_DATABASE_SSLROOTCERTPath 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.

  1. 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.

  2. Create both databases, owned by the application role. The orchestrator uses a second database named after the first with a -ddorch suffix, 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>";
  3. 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>";
  4. 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 and system:serviceaccount:<namespace>:<release>-webhook-gateway for the gateway, audience api://AzureADTokenExchange.

  • Annotations: annotate each ServiceAccount with azure.workload.identity/client-id: <the identity's client ID>. DefectDojo adds the azure.workload.identity/use label 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.user for the application and webhookGateway.database.role for the gateway.

  • Extension: allow-list it first by setting the server parameter azure.extensions to PG_TRGM.

  • Schema: in a new database on Azure, the public schema belongs to azure_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-full with sslRootCert: /etc/ssl/certs/ca-certificates.crt works.

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 when rds_iam is granted, its password stops working. In that case, an IAM principal allowed rds-db:connect for the master user can sign in with a token from aws rds generate-db-auth-token --username <master user> and revoke the membership.

  • IAM roles: give each identity an IAM role allowed rds-db:connect on arn:aws:rds-db:<region>:<account>:dbuser:<DbiResourceId>/<role name>. Bind it to its ServiceAccount through the cluster’s OIDC provider (IRSA: annotate the ServiceAccount with eks.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 the sslRootCertSecret Secret, and use sslMode: 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.instanceUser and roles/cloudsql.client. Bind each to its Kubernetes ServiceAccount with roles/iam.workloadIdentityUser, and annotate the ServiceAccount with iam.gke.io/gcp-service-account: <service account email>.

  • Database users: create each one without the .gserviceaccount.com suffix:

    gcloud sql users create <sa>@<project>.iam --instance <instance> --type cloud_iam_service_account

    <sa>@<project>.iam is the role name for database.user and webhookGateway.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 the sslRootCertSecret Secret, and use sslMode: verify-ca. That CA signs only this instance’s certificate, so verify-ca already pins the connection to it. verify-full fails when connecting by IP address, because the certificate names the instance’s *.sql.goog DNS name instead; use it only when database.host is 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

  1. Create two more service accounts, for the sandbox’s app role and admin role, and give each roles/cloudsql.instanceUser and roles/cloudsql.client.

  2. 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>
  3. Create a Cloud SQL IAM user for each, as for the application (gcloud sql users create <sa>@<project>.iam --type cloud_iam_service_account).

  4. As the postgres user, 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";
  5. 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

  1. 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.

  2. 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>";
  3. Set sandbox.auth.appUser and sandbox.auth.adminUser to the identities’ names and sandbox.auth.appAzureClientId and sandbox.auth.adminAzureClientId to 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.

  1. Allow the application’s IAM role to sign in as the two sandbox roles. Their names come from the database name, so for dojodb they are dojodb_sandbox and dojodb_sandbox_admin. Add both to its rds-db:connect policy 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_admin
  2. As 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_iam is 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 holds rds_iam, so its password keeps working.

  3. Set sandbox.enabled: true. Nothing goes under sandbox.auth on 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

  1. Upgrade the install on password authentication first, so every migration is applied.

  2. Prepare the identities and roles as above.

  3. As an administrator, move ownership in each database:

    REASSIGN OWNED BY <old password user> TO "<app role>";
  4. Switch database.auth.mode and remove the password settings.

Troubleshooting

ErrorCause
Password returned by client is emptyThe 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 encryptionTLS is off. Set sslMode to require or stricter.
permission denied to create database from ddorchCreate the -ddorch database in advance, owned by the application role.