Skip to content
Draft
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
23 changes: 11 additions & 12 deletions Cargo.lock

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

32 changes: 32 additions & 0 deletions integration/vault/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
services:
postgres:
image: postgres:18
environment:
POSTGRES_DB: pgdog
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "15432:5432"
volumes:
- ./setup.sql:/docker-entrypoint-initdb.d/setup.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 2s
retries: 10

vault:
image: hashicorp/vault:1.17
environment:
VAULT_DEV_ROOT_TOKEN_ID: "root-token"
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
VAULT_ADDR: "http://0.0.0.0:8200"
ports:
- "18200:8200"
cap_add:
- IPC_LOCK
healthcheck:
test: ["CMD", "vault", "status"]
interval: 2s
timeout: 2s
retries: 10
34 changes: 34 additions & 0 deletions integration/vault/pgdog.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#:schema ./.schema/pgdog.schema.json
#
# PgDog configuration.
[general]
host = "0.0.0.0"
port = 6432
workers = 2
default_pool_size = 10
min_pool_size = 1
pooler_mode = "transaction"
tls_verify = "disabled"
shutdown_timeout = 60_000
shutdown_termination_timeout = 60_000
openmetrics_port = 9090
openmetrics_namespace = "pgdog_"
log_format = "json"
log_level = "info"
passthrough_auth = "disabled"
connect_attempt_delay = 1_000
auth_type = "scram"

[[databases]]
name = "pgdog"
host = "127.0.0.1"
port = 15432
role = "primary"

[vault]
address = "http://127.0.0.1:18200"
auth_method = "app_role"
tls_verify = "disable"
# Run integration/vault/setup-vault.sh and paste the output here.
role_id = "<YOUR_ROLE_ID>"
secret_id = "<YOUR_SECRET_ID>"
94 changes: 94 additions & 0 deletions integration/vault/setup-vault.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -euo pipefail

## Configure Vault's database secrets engine for the pgdog vault integration.
## Run after docker compose up.

export VAULT_ADDR="http://127.0.0.1:18200"
export VAULT_TOKEN="root-token"

echo "==> Enabling database secrets engine..."
vault secrets enable database 2>/dev/null || echo " (already enabled)"

echo "==> Configuring PostgreSQL connection..."
vault write database/config/pgdog \
plugin_name=postgresql-database-plugin \
allowed_roles="pgdog-admin,dml-role,ddl-role,readonly-role" \
connection_url="postgresql://{{username}}:{{password}}@host.docker.internal:15432/pgdog?sslmode=disable" \
username="postgres" \
password="postgres"

echo "==> Creating DML role (30m TTL)..."
vault write database/roles/dml-role \
db_name=pgdog \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' IN ROLE \"dml_role\";" \
revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="30m" \
max_ttl="1h"

echo "==> Creating DDL role (30m TTL)..."
vault write database/roles/ddl-role \
db_name=pgdog \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' IN ROLE \"ddl_role\";" \
revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="30m" \
max_ttl="1h"

echo "==> Creating readonly role (30m TTL)..."
vault write database/roles/readonly-role \
db_name=pgdog \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' IN ROLE \"readonly_role\";" \
revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="30m" \
max_ttl="1h"

echo "==> Creating pgdog-admin role (for pg_authid access, 30m TTL)..."
vault write database/roles/pgdog-admin \
db_name=pgdog \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' IN ROLE \"pgdog_admin_role\"; GRANT pg_read_all_data TO \"{{name}}\";" \
revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="30m" \
max_ttl="1h"

echo "==> Enabling AppRole auth..."
vault auth enable approle 2>/dev/null || echo " (already enabled)"

echo "==> Creating pgdog AppRole..."
vault write auth/approle/role/pgdog \
token_ttl=1h \
token_max_ttl=4h \
token_policies="pgdog-policy"

echo "==> Creating pgdog policy..."
vault policy write pgdog-policy - <<'POLICY'
path "database/creds/*" {
capabilities = ["read"]
}

# Renew leases
path "sys/leases/renew" {
capabilities = ["update"]
}
POLICY

echo "==> Fetching AppRole credentials..."
ROLE_ID=$(vault read -field=role_id auth/approle/role/pgdog/role-id)
SECRET_ID=$(vault write -field=secret_id -f auth/approle/role/pgdog/secret-id)

echo ""
echo "============================================"
echo " Vault setup complete!"
echo "============================================"
echo ""
echo " AppRole role_id: $ROLE_ID"
echo " AppRole secret_id: $SECRET_ID"
echo ""
echo " Test credential generation:"
echo " vault read database/creds/dml-role"
echo " vault read database/creds/ddl-role"
echo " vault read database/creds/readonly-role"
echo ""
echo " To use with pgdog, update integration/vault/pgdog.toml with:"
echo " role_id = \"$ROLE_ID\""
echo " secret_id = \"$SECRET_ID\""
echo ""
28 changes: 28 additions & 0 deletions integration/vault/setup.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- Creates the parent roles that Vault's IN ROLE clause references.

-- DML role: read/write on application tables
CREATE ROLE dml_role NOLOGIN;
GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA public TO dml_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT,INSERT,UPDATE,DELETE ON TABLES TO dml_role;
GRANT USAGE,SELECT,UPDATE ON ALL SEQUENCES IN SCHEMA public TO dml_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE,SELECT,UPDATE ON SEQUENCES TO dml_role;

-- DDL role: schema migrations
CREATE ROLE ddl_role NOLOGIN;
GRANT ALL ON SCHEMA public TO ddl_role;
GRANT ALL ON ALL TABLES IN SCHEMA public TO ddl_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ddl_role;

-- Readonly role: analytics/reporting
CREATE ROLE readonly_role NOLOGIN;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_role;

-- Create a sample table for testing
CREATE TABLE IF NOT EXISTS demo_items (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);

INSERT INTO demo_items (name) VALUES ('item-1'), ('item-2'), ('item-3');
17 changes: 17 additions & 0 deletions integration/vault/users.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#:schema ./.schema/users.schema.json
#
# Basic users configuration.
#
[[users]]
name = "dml_role"
password = "pgdog"
database = "pgdog"
server_auth = "vault"
vault_path = "database/creds/dml-role"

[[users]]
name = "ddl_role"
password = "pgdog"
database = "pgdog"
server_auth = "vault"
vault_path = "database/creds/ddl-role"
8 changes: 8 additions & 0 deletions pgdog-config/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use super::replication::{MirrorConfig, Mirroring, MirroringLevel, ReplicaLag, Re
use super::rewrite::Rewrite;
use super::sharding::{ManualQuery, OmnishardedTables, ShardedMapping, ShardedTable};
use super::users::{Admin, Plugin, Users};
use super::vault::VaultConfig;

#[derive(Debug, Clone, PartialEq)]
pub struct ConfigAndUsers {
Expand Down Expand Up @@ -222,6 +223,13 @@ pub struct Config {
#[schemars(default = "crate::users::Admin::schemars_default_stub")]
pub admin: Admin,

/// Vault integration for dynamic credential rotation.
///
/// When set, pgdog fetches PostgreSQL credentials from Vault for any pool
/// that has `vault_path` configured in `users.toml`.
#[serde(default)]
pub vault: Option<VaultConfig>,

/// To detect and route queries with sharding keys, PgDog expects the sharded column to be specified in the configuration.
///
/// https://docs.pgdog.dev/configuration/pgdog.toml/sharded_tables/
Expand Down
2 changes: 2 additions & 0 deletions pgdog-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod system_catalogs;
pub mod url;
pub mod users;
pub mod util;
pub mod vault;

pub use auth::{AuthType, PassthroughAuth};
pub use core::{Config, ConfigAndUsers};
Expand All @@ -36,6 +37,7 @@ pub use rewrite::{Rewrite, RewriteMode};
pub use sharding::*;
pub use system_catalogs::system_catalogs;
pub use users::{Admin, Plugin, ServerAuth, User, Users};
pub use vault::{VaultAuthMethod, VaultConfig, VaultTlsVerify};

use std::time::Duration;

Expand Down
11 changes: 11 additions & 0 deletions pgdog-config/src/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ pub enum ServerAuth {
RdsIam,
/// Generate an Azure Workload Identity auth token per connection attempt.
AzureWorkloadIdentity,
/// Credentials are managed by the Vault integration. pgdog fetches
/// `server_user` and `server_password` from Vault at runtime; never
/// falls back to static client passwords for backend connections.
Vault,
}

impl ServerAuth {
Expand Down Expand Up @@ -284,6 +288,13 @@ pub struct User {
pub two_phase_commit_auto: Option<bool>,
/// Server connections older than this (in milliseconds) will be closed when returned to the pool.
pub server_lifetime: Option<u64>,
/// Vault database credential path for this pool, e.g. `database/creds/dml-role`.
///
/// When set, pgdog manages backend credentials via Vault: it fetches a fresh
/// username/password from this path and rotates them before the lease expires.
/// The `[vault]` block in `pgdog.toml` must also be configured.
#[serde(default)]
pub vault_path: Option<String>,
}

impl User {
Expand Down
Loading
Loading