Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ integration/**/*.test
enterprise/control-web/dist
enterprise/control-web/node_modules
enterprise/control-web/.vite/*

.tokensave
2 changes: 1 addition & 1 deletion .schema/pgdog.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2218,7 +2218,7 @@
]
},
"Vault": {
"description": "HashiCorp Vault settings, used by pools configured with `server_auth = \"vault\"`.\n\nPgDog logs into Vault using the configured auth method and fetches\ndynamic database credentials from the per-user `vault_path`.",
"description": "HashiCorp Vault settings, used by pools configured with `server_auth = \"vault_dynamic\"`\nor `\"vault_static\"`.\n\nPgDog logs into Vault using the configured auth method and fetches\ndatabase credentials from the per-user `server_vault_path`.",
"type": "object",
"properties": {
"approle_role_id": {
Expand Down
18 changes: 15 additions & 3 deletions .schema/users.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,14 @@
"const": "azure_workload_identity"
},
{
"description": "Fetch dynamic credentials from HashiCorp Vault.",
"description": "Fetch dynamic credentials from HashiCorp Vault (database secrets engine).\nVault generates a new username and password on each lease.",
"type": "string",
"const": "vault"
"const": "vault_dynamic"
},
{
"description": "Fetch credentials for a Vault static database role.\nVault manages password rotation; the username is fixed.",
"type": "string",
"const": "vault_static"
}
]
},
Expand Down Expand Up @@ -270,6 +275,13 @@
"null"
]
},
"server_vault_path": {
"description": "Vault path used to fetch backend (server-side) database credentials,\ne.g. `database/creds/my-role` for `server_auth = \"vault_dynamic\"` or\n`database/static-creds/my-role` for `server_auth = \"vault_static\"`.",
"type": [
"string",
"null"
]
},
"statement_timeout": {
"description": "Statement timeout.\n\nSets the `statement_timeout` on all server connections at connection creation. This allows you to set a reasonable default for each user without modifying `postgresql.conf` or using `ALTER USER`.\n\n**Note:** Nothing is preventing the user from manually changing this setting at runtime, e.g., by running `SET statement_timeout TO 0`;\n\nhttps://docs.pgdog.dev/configuration/users.toml/users/#statement_timeout",
"type": [
Expand All @@ -294,7 +306,7 @@
]
},
"vault_path": {
"description": "Vault path to fetch dynamic database credentials from, e.g. `database/creds/my-role`.\nRequired when `server_auth` is set to `vault`.",
"description": "Vault path to a static database role used to verify client passwords,\ne.g. `database/static-creds/my-role`. When set, PgDog fetches the\ncurrent password from Vault and compares it to what the client\nprovides instead of using a statically configured password.",
"type": [
"string",
"null"
Expand Down
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,13 @@ role = "auto"

📘 **[Authentication](https://docs.pgdog.dev/features/authentication/)**

PgDog supports four authentication methods:
PgDog supports five authentication methods:

1. Password-based
2. AWS RDS IAM
3. Azure Workload Identity
4. HashiCorp Vault dynamic credentials
5. HashiCorp Vault static role credentials

#### Password-based authentication

Expand Down Expand Up @@ -253,7 +254,7 @@ When any user has `server_auth = "azure_workload_identity"`, the following setti
- `tls_verify` must **not** be `"disabled"`.
- `passthrough_auth` must be `"disabled"`.

#### HashiCorp Vault authentication
#### HashiCorp Vault dynamic role authentication

PgDog can fetch dynamic database credentials (username and password) from HashiCorp Vault's database secrets engine, while keeping client-to-PgDog authentication unchanged. Credentials are cached and rotated automatically after a configured percentage of the Vault lease has elapsed.

Expand All @@ -266,8 +267,8 @@ In `users.toml`:
name = "alice"
database = "pgdog"
password = "client-password"
server_auth = "vault"
vault_path = "database/creds/pgdog"
server_auth = "vault_dynamic"
server_vault_path = "database/creds/pgdog"
# Refresh credentials after 80% of the lease has elapsed (default).
# vault_refresh_percent = 80
```
Expand All @@ -283,11 +284,36 @@ kubernetes_role = "pgdog"

PgDog logs into Vault with Kubernetes auth (using the pod's service account JWT) or AppRole (`approle_role_id` plus `approle_secret_id_file` or the `VAULT_SECRET_ID` environment variable).

When any user has `server_auth = "vault"`, the following settings must be configured as well:
When any user has `server_auth = "vault_dynamic"` or `"vault_static"`, the following settings must be configured as well:

- `tls_verify` must **not** be `"disabled"`.
- `passthrough_auth` must be `"disabled"`.

#### HashiCorp Vault static role authentication

Unlike dynamic credentials, a Vault static database role has a fixed username and only its password rotates, on a schedule Vault manages. PgDog supports two independent uses of a static role, they don't need to point at the same role, and each has its own username setting:

- `vault_path`: verify the password a client sends to PgDog against Vault's current password for the role, instead of a statically configured password.
- `server_auth = "vault_static"` with `server_vault_path`: use the role's Vault-managed password for PgDog-to-PostgreSQL connections. Unlike `vault_dynamic`, PgDog doesn't take the username from Vault, it connects as `server_user` or `name`, if `server_user` isn't set.

**Example**

In `users.toml`, for a client authenticating as `alice` (verified against a static role registered under that same name) while PgDog connects to Postgres as `pgdog_service` (a separate static role):

```toml
[[users]]
name = "alice"
database = "pgdog"
vault_path = "database/static-creds/alice"
server_user = "pgdog_service"
server_auth = "vault_static"
server_vault_path = "database/static-creds/pgdog-service"
```

In `pgdog.toml`, the same `[vault]` section used for dynamic credentials applies.

Both settings are optional and independent: set only `vault_path` to verify client passwords while keeping any other backend authentication method, or only `server_auth = "vault_static"` to use a static role for backend connections while clients authenticate with a regular password.

### Sharding

📘 **[Sharding](https://docs.pgdog.dev/features/sharding/)**
Expand Down
3 changes: 2 additions & 1 deletion example.pgdog.toml
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,8 @@ fingerprint = "2d9944fc9caeaadd" # [3285733254894627549]
# exposure = 0.5 # Optional: overrides general.mirror_exposure

# HashiCorp Vault settings, required when any user in users.toml
# sets `server_auth = "vault"`.
# sets `server_auth = "vault_dynamic"` or `"vault_static"`, or configures
# `vault_path` for client-side static role password verification.
#
# [vault]
# url = "https://vault.internal:8200"
Expand Down
19 changes: 16 additions & 3 deletions example.users.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,21 @@ password = "pgdog"

# Example: backend authentication with HashiCorp Vault dynamic credentials.
# Requires the [vault] section in pgdog.toml. PgDog fetches a generated
# username and password from `vault_path` and rotates them after
# username and password from `server_vault_path` and rotates them after
# `vault_refresh_percent` of the lease has elapsed.
# server_auth = "vault"
# vault_path = "database/creds/pgdog"
# server_auth = "vault_dynamic"
# server_vault_path = "database/creds/pgdog"
# vault_refresh_percent = 80 # optional; default 80

# Example: client authentication against a HashiCorp Vault static database role.
# Requires the [vault] section in pgdog.toml. PgDog verifies the password the
# client sends against Vault's current password for the static role at `vault_path`,
# instead of a statically configured password.
# Independent of `server_auth` below; combine it with any backend authentication method.
# vault_path = "database/static-creds/alice"

# Example: backend authentication with a HashiCorp Vault static role.
# Requires the [vault] section in pgdog.toml.
# server_user = "pgdog_service"
# server_auth = "vault_static"
# server_vault_path = "database/static-creds/pgdog-service"
21 changes: 20 additions & 1 deletion integration/vault/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ $VAULT secrets enable database 2>/dev/null || true

$VAULT write database/config/pgdog \
plugin_name=postgresql-database-plugin \
allowed_roles="pgdog-role" \
allowed_roles="pgdog-role,pgdog-static-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/pgdog?sslmode=disable" \
username="postgres" \
password="postgres"
Expand All @@ -33,13 +33,29 @@ $VAULT write database/roles/pgdog-role \
default_ttl="10m" \
max_ttl="30m"

echo "Creating static Postgres user for Vault static role..."
docker compose exec -T postgres psql -U postgres -c \
"CREATE USER pgdog_static WITH LOGIN PASSWORD 'initial_password'; \
GRANT ALL PRIVILEGES ON DATABASE pgdog TO pgdog_static;" \
2>/dev/null || true

echo "Configuring Vault static database role..."
$VAULT write database/static-roles/pgdog-static-role \
db_name=pgdog \
rotation_statements="ALTER USER \"{{name}}\" WITH PASSWORD '{{password}}';" \
username="pgdog_static" \
rotation_period=300

echo "Configuring AppRole auth..."
$VAULT auth enable approle 2>/dev/null || true

$VAULT policy write pgdog-policy - <<'EOF'
path "database/creds/pgdog-role" {
capabilities = ["read"]
}
path "database/static-creds/pgdog-static-role" {
capabilities = ["read"]
}
EOF

$VAULT write auth/approle/role/pgdog-role \
Expand All @@ -49,6 +65,7 @@ $VAULT write auth/approle/role/pgdog-role \

ROLE_ID=$($VAULT read -field=role_id auth/approle/role/pgdog-role/role-id)
SECRET_ID=$($VAULT write -f -field=secret_id auth/approle/role/pgdog-role/secret-id)
STATIC_PWD=$($VAULT read -field=password database/static-creds/pgdog-static-role)

echo "$SECRET_ID" > "$SCRIPT_DIR/vault-secret-id"
echo "Written vault-secret-id"
Expand Down Expand Up @@ -78,5 +95,7 @@ EOF

echo "Generated pgdog.toml with role_id=$ROLE_ID"
echo ""
echo "Static role password is $STATIC_PWD"
echo ""
echo "Run pgdog with:"
echo " cargo run -- --config $SCRIPT_DIR/pgdog.toml --users $SCRIPT_DIR/users.toml"
13 changes: 11 additions & 2 deletions integration/vault/users.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@
name = "pgdog"
database = "pgdog"
password = "pgdog"
server_auth = "vault"
vault_path = "database/creds/pgdog-role"
server_auth = "vault_dynamic"
server_vault_path = "database/creds/pgdog-role"

# Client password verified against Vault static role; backend also uses the
# static role so the same Vault-managed password is used end-to-end.
[[users]]
name = "pgdog_static"
database = "pgdog"
vault_path = "database/static-creds/pgdog-static-role"
server_auth = "vault_static"
server_vault_path = "database/static-creds/pgdog-static-role"
Loading
Loading