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
96 changes: 96 additions & 0 deletions .schema/pgdog.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,17 @@
"time": null,
"user_timeout": null
}
},
"vault": {
"description": "HashiCorp Vault settings, required for users configured with `server_auth = \"vault\"`.",
"anyOf": [
{
"$ref": "#/$defs/Vault"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -2069,6 +2080,91 @@
}
]
},
"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`.",
"type": "object",
"properties": {
"approle_role_id": {
"description": "AppRole auth: role ID.",
"type": [
"string",
"null"
]
},
"approle_secret_id_file": {
"description": "AppRole auth: path to a file containing the secret ID.\n\n**Note:** If not set, the secret ID is read from the `VAULT_SECRET_ID`\nenvironment variable.",
"type": [
"string",
"null"
]
},
"auth_method": {
"description": "Auth method used to log into Vault.",
"$ref": "#/$defs/VaultAuthMethod"
},
"auth_mount": {
"description": "Mount path of the auth method.\n\n_Default:_ `kubernetes` for Kubernetes auth, `approle` for AppRole auth.",
"type": [
"string",
"null"
]
},
"client_token_ttl": {
"description": "Seconds before the Vault client token expires to trigger a re-login.\n\n_Default:_ `60`",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"kubernetes_jwt_path": {
"description": "Kubernetes auth: path to the service account JWT.\n\n_Default:_ `/var/run/secrets/kubernetes.io/serviceaccount/token`",
"type": [
"string",
"null"
]
},
"kubernetes_role": {
"description": "Kubernetes auth: name of the Vault role to log in as.",
"type": [
"string",
"null"
]
},
"namespace": {
"description": "Vault namespace (Vault Enterprise). Sent as the `X-Vault-Namespace` header.",
"type": [
"string",
"null"
]
},
"url": {
"description": "Vault server URL, e.g. `https://vault.internal:8200`.",
"type": "string"
}
},
"additionalProperties": false,
"required": [
"url",
"auth_method"
]
},
"VaultAuthMethod": {
"description": "How PgDog authenticates to Vault.",
"oneOf": [
{
"description": "Kubernetes auth: log in with the pod's service account JWT.",
"type": "string",
"const": "kubernetes"
},
{
"description": "AppRole auth: log in with a role ID and secret ID.",
"type": "string",
"const": "approle"
}
]
},
"Vector": {
"type": "object",
"properties": {
Expand Down
22 changes: 22 additions & 0 deletions .schema/users.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
"description": "Generate an Azure Workload Identity auth token per connection attempt.",
"type": "string",
"const": "azure_workload_identity"
},
{
"description": "Fetch dynamic credentials from HashiCorp Vault.",
"type": "string",
"const": "vault"
}
]
},
Expand Down Expand Up @@ -287,6 +292,23 @@
"boolean",
"null"
]
},
"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`.",
"type": [
"string",
"null"
]
},
"vault_refresh_percent": {
"description": "Percentage of the Vault credential lease after which credentials are refreshed.\n\n_Default:_ `80`",
"type": [
"integer",
"null"
],
"format": "uint8",
"maximum": 255,
"minimum": 0
}
},
"additionalProperties": false,
Expand Down
68 changes: 68 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,12 @@ role = "auto"

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

PgDog supports three authentication methods:
PgDog supports four authentication methods:

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

#### Password-based authentication

Expand Down Expand Up @@ -252,6 +253,41 @@ 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

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.

**Example**

In `users.toml`:

```toml
[[users]]
name = "alice"
database = "pgdog"
password = "client-password"
server_auth = "vault"
vault_path = "database/creds/pgdog"
# Refresh credentials after 80% of the lease has elapsed (default).
# vault_refresh_percent = 80
```

In `pgdog.toml`:

```toml
[vault]
url = "https://vault.internal:8200"
auth_method = "kubernetes" # or "approle"
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:

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

### Sharding

📘 **[Sharding](https://docs.pgdog.dev/features/sharding/)**
Expand Down
17 changes: 17 additions & 0 deletions example.pgdog.toml
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,20 @@ fingerprint = "2d9944fc9caeaadd" # [3285733254894627549]
# destination_db = "pgdog_mirror"
# queue_length = 256 # Optional: overrides general.mirror_queue
# exposure = 0.5 # Optional: overrides general.mirror_exposure

# HashiCorp Vault settings, required when any user in users.toml
# sets `server_auth = "vault"`.
#
# [vault]
# url = "https://vault.internal:8200"
# Auth method used to log into Vault: "kubernetes" or "approle".
# auth_method = "kubernetes"
# auth_mount = "kubernetes" # optional; defaults to the auth method name
# kubernetes_role = "pgdog"
# kubernetes_jwt_path = "/var/run/secrets/kubernetes.io/serviceaccount/token" # optional
# For AppRole auth:
# auth_method = "approle"
# approle_role_id = "..."
# approle_secret_id_file = "/etc/pgdog/vault-secret-id" # or set VAULT_SECRET_ID env var
# Vault namespace (Vault Enterprise), optional.
# namespace = "my-namespace"
8 changes: 8 additions & 0 deletions example.users.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ password = "pgdog"
# PgDog still authenticates the client as configured by `general.auth_type`;
# this only affects how PgDog authenticates to PostgreSQL servers.
# server_auth = "azure_workload_identiy"

# 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
# `vault_refresh_percent` of the lease has elapsed.
# server_auth = "vault"
# vault_path = "database/creds/pgdog"
# vault_refresh_percent = 80 # optional; default 80
2 changes: 2 additions & 0 deletions integration/vault/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pgdog.toml
vault-secret-id
29 changes: 29 additions & 0 deletions integration/vault/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
services:
postgres:
image: postgres:17
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: pgdog
ports:
- "5450:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10

vault:
image: hashicorp/vault:1.17
command: server -dev -dev-root-token-id=root
environment:
VAULT_DEV_ROOT_TOKEN_ID: root
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
VAULT_ADDR: "http://127.0.0.1:8200"
cap_add:
- IPC_LOCK
ports:
- "8200:8200"
healthcheck:
test: ["CMD", "vault", "status"]
interval: 5s
retries: 10
start_period: 5s
Loading
Loading