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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,8 @@ Hopefully the information above will give you everything you need to configure _
## Testing

The validator has a basic test suite, documented under [test/README.md](test/README.md).

## Examples

Examples can be found in the `examples` folder.
Currently there's only a single [keycloak](examples/keycloak/README.md) example.
54 changes: 54 additions & 0 deletions examples/keycloak/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Keycloak Example for pg_oidc_validator

This example provides a demo environment with Keycloak and PostgreSQL configured for OAuth authentication using Docker Compose.

**Notes:**
* This is only a demo environment, with self-signed certificates.
Do not use for production.
* This example doesn't use persistent volumes. When you stop the containers, all data is lost.

## Prerequisites

### Required software

Docker with Compose V2 installed (`docker compose` command)

### Add keycloak to hosts file

For the OAuth device flow to work in your browser, add `keycloak` to your hosts file:

**On Linux/Mac:**
```bash
echo "127.0.0.1 keycloak" | sudo tee -a /etc/hosts
```

**On Windows:** Edit `C:\Windows\System32\drivers\etc\hosts` as Administrator and add:
```
127.0.0.1 keycloak
```

This allows your browser to resolve the `https://keycloak:8443` URL used in the OAuth device flow.

## Quick Start

```bash
# Start Keycloak and PostgreSQL
docker compose up -d

# Wait for services to be ready
# Optional: watch the logs while waiting
docker compose logs -f

# Once ready, run the interactive psql client
# Follow the OAuth device flow, login with `testuser` / `asdfasdf`
docker compose run --rm psql-client

# When done, stop the services
docker compose down
```

## Keycloak admin interface access

Open https://keycloak:8443 in your browser:
- Username: `admin`
- Password: `admin`
34 changes: 34 additions & 0 deletions examples/keycloak/certs/crt.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
6 changes: 6 additions & 0 deletions examples/keycloak/certs/gencert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
# Command to generate the files in the certs folder.
# Since they have a 10 year lifetime, running this isn't necessary for a long time.
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out crt.pem -sha256 -days 3650 -nodes \
-subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=keycloak" \
-addext "subjectAltName=DNS:keycloak,DNS:localhost,IP:127.0.0.1"
52 changes: 52 additions & 0 deletions examples/keycloak/certs/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCn0eNR9nd7jQd9
r1nGIm8+w/1+Nn/tAoH4EJomo/JslvX5DUbh9E2svrXV5UlEFHT1F/eGUfh7n+Kw
dl5nHdKIYqr47cITbBwbWYX2NDgpQsGowVpPJPg3muEUsZksxq/kVGnv30DgGv4w
Bz65cNSb+W2mnK6EuluAqEK68YNgHHQqIoiYbKwxSn7ey/TLbn8WH3blyb6zN/m7
W2q6r8TBuYk7mXWsF1oRBKi7q1iR1mf+TD+r1ttu++uD4ICbN7v5M52GIu8iM/d/
4DgNt+1mNwojyHfCTPyjE4hY4mnI2csPNONO2SK4bO+ExTHcZs7oaPYrzV7N0OxO
cO1mAn4jC8qdW6F0FIGYNn7bQykDwdmnv+JLGVY8bMyUF9qJFe9bVlmDtcEO5zCl
tsHqW17oZh5a/p2AudyiqdETCGPZrTrWfRRa+oK7KoaLCxK2SYDhNtGFTo6vfO4K
5uFc1w1gsMqq1cD3aUk3IxGdo/h98nMr1NyIeCzSYicj8c3BrcYxPmN2WbtU8V7a
vaWYgUJp4dCFV/WkYTX1UnXVTgiyHRCbH/Z6tChTvJFG/2j8ipZ2gglt3Qjp8qfA
yBjkPphncg3x+7Tl/LGdCFk6yE4lIuxmDiVNkghzKMkFKBzVJrUTxaMTT2NHwUiy
ia7j2DsFhVSoZ1lX3q13m1SytN3mHQIDAQABAoICAAzCvAWVXdd75uTRMQ/Zhw3F
15NbYu9rZrH6PTND+Ydbrgw7TLdnHRyBPqorIZ7DuXL4WN6sG/gptUVUWT0ZB1NO
J8PLr5t/d6tCuIBHzzkGXChpvDHsoaBeYZzYgxYX0419BIMnV2af1tH7GXNk8RDp
7sMINe/Ev3fXrR9LuJqI/1iqDV3qh+gr/g93gPLF2to2F60R+2ZJ36G19umHVPw8
3Rw2Wokn5QLXQ/QYa6DHP1tJ6MUdwZi9hVaFR/Ir7KcRz2AN08YmuNXOsr4Ks5c3
bZXaXltDASpueuP072gRqEwAA9Eqiv/TOx4K5CdnFkOVof/wDhqzULMHb9D3NPUN
e4+eLqzHNQWcdu+OjduUJmQdOdk9Iadsv5AF/YFGcDMbxoq1GoGVSOoi+xQgGzhh
4Bj3DbFrl0dx2gbvGqYBXxmpWR84C9d3WZ3SqMS601Onl7SzPNt28C1Vk9FWI3u9
rkrJgEnh8+EF7IKJ8HpD8qrvQXPdMa1/BZdrZNJKGbiPNbF2KjW5NugdaV0jokC4
pJZUTFQb704ni0YZEJE5Crmq2XW8s1PjxJ3Lz0jKjNfwmQxWhD6Km+6138KYhWJo
VbCcqqzxd4rYgR0wxS4nMgq1SxmSGeY6iYhuRT7RREF7ED2oMtRKpZ2MXVgk0Q1x
pPxnrhToxP+JYS6mrVBHAoIBAQDaun0xHeflTHJ6oDS7RtTOdHfOrpwfMKjF7lHS
lZ7lTjVVkwVoYtiYYhN60U+/dDpt3iqoUUocNfaGBLuCF1QJjyb0dCSARJnir4Dd
qwDrTQ3Dc9UX3W/xBlGdskihVzhA7h6omU95K1AORJ+oxnJUDdauaRr8iOTC3sJ1
zd4jGAxv9sdJHyNV36ji2+GqFFh7vxRR9rFnhf2OZ+P5P7LiG4oHNHO8ygzBll5u
fR0i90JEVSh5Ra5QFusulKlMk0TTOyTug9XpLcuocvMalOj/PRSKyj9gsi9rfUTA
NW0MP9puZ9rwR5qb7ACwEJzLlwVDNQM8M98mIfcwkqz3K1O7AoIBAQDEaqHPcANU
2Pm+vS5KfmXGLKZJOU99GTQfAaUjOps21iLF5VPUvI/WtJYc6R+5TycWtxIjZVCZ
s1v/o6LDBrKBmck/cVaCYxn7LIdR3SzfvkX+LIvxbNTZFBw4N+TCl4qitRiIvx2c
2xB4n9KwGDM5k9CEkcWX1ggK6XbsKDNy93Ksfv0wE6IgFdnFoQu+Ezflk7yWLXNH
GoX4WWpperpXLamRoPMiPVqNnWYLg/k+LwzWP2DZgIdzm7wC2v+XdUcmVi/BBL0n
uajm1D0SOSoUEXFtr84TZ3+Mq2bn+CjYAUfx1+B1EfZl3SedhN+1CW+sUpKK/wPu
GsQxVCHELBQHAoIBAQCAQmbzDFRVM1TV72e8gbZ8MfOnMOC/sWrmVe+JCs+YKxo/
Se0b3wqfoLNHi8G1xNQWZPaiqLhKfqJGyDOj/0X6LQVsx0Y4KQIL2Vo5ofLyB8cQ
W7YTPnhL36awUEEiyuBjwr4bo3rk1K8nDrqI34VfIJZIw71dZHCwjKt/JO8jqRBx
/0Ww7R9tVa/VB5b2guO3/L5Pqdcxm3KptOYL3Hxq9jckLm/HvrtoMWLCa39QZ6lm
JIGNS30B3c6fC/GSw7DSJJZtfsVK7N3Fs1I2vic6tHh9QkeTzijcYSKViz6ctjzC
DQhnabWRxPxKQhOPlskxNb7l6Izr8XLf+sKOVcvNAoIBACTNtjQgUP99CI7s89eR
h5BynVXrHzHZnyKQNFk9igfkZ++c4PBjxK/+doJETGV0p2ZiN0vamBe0u/BSwRS6
FIikQEla+1LDLwMZfOGiB96E0KinwDEkq11hn0gJcRvlOVzzgf1dkjbp9VQk3l2Q
q0iGofO1PMkOmcMxq87kWX+ZTit0QAzaIO7SKVQWsRSUlUy3OgcJzSftmFzIpF/P
V0suiy92cRhhVq5iZ9SQjgtQ1Z7vkT4wDzFiZQBD+NBwcTyFubz5HlhrOXLHIgpg
G7pW6mIbJwoLwqKhG08r+LtAwjJWuQA2tWyw29NwKlrJwdsQPdU9o4biDRERKqKP
f9ECggEAVi+AT1G6+fUBn5bEJwQy9fpL+U1AC/XMjPLUIrHAWLw7X4RlXnXzGK24
+1eYTTrOHTRUVSKcTQXu4yWFqR0sjjgI3/FwCCAV7R4bh67nnBT1batru+VmY/sn
9SFroCAMAxLx4s3/94bHECc4A2yonxgZzxSXKTB5/6MM+PcJP0X2oXDFZbRr9YD7
c9xuQgJ61lbG3Y6cnjoUc5/xJysOT7uRTfKVL6haWuOUh6LyZaDBY1bDMRST5W1g
o6DCbFPHJduZ0A/Tpwojg3ZcxP82R/853atXZmlUuDjFHee1AWiLRJP60FqESx5A
Z0chEXWiWdvIrJ0PVV304gW1cdzvQQ==
-----END PRIVATE KEY-----
121 changes: 121 additions & 0 deletions examples/keycloak/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
services:
keycloak:
image: quay.io/keycloak/keycloak:latest
hostname: keycloak
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
KC_HTTPS_CERTIFICATE_FILE: /keys/crt.pem
KC_HTTPS_CERTIFICATE_KEY_FILE: /keys/key.pem
KC_HEALTH_ENABLED: "true"
KC_HOSTNAME: keycloak
KC_HOSTNAME_PORT: 8443
KC_HOSTNAME_STRICT: "false"
KC_HOSTNAME_STRICT_BACKCHANNEL: "true"
volumes:
- ../../test/import:/opt/keycloak/data/import
- ./certs:/keys
ports:
- "127.0.0.1:8443:8443"
command: start-dev --import-realm
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8443"]
interval: 5s
timeout: 3s
retries: 60
start_period: 30s

postgres:
image: docker.io/postgres:18
environment:
POSTGRES_HOST_AUTH_METHOD: trust
volumes:
- ./certs/crt.pem:/usr/local/share/ca-certificates/keycloak-test.crt:ro
ports:
- "127.0.0.1:15432:5432"
depends_on:
keycloak:
condition: service_healthy
entrypoint:
- bash
- -c
- |
apt-get update -qq
apt-get install -y -qq ca-certificates curl libcurl4 wget
update-ca-certificates

wget -q https://github.com/Percona-Lab/pg_oidc_validator/releases/download/latest/pg-oidc-validator-pgdg18.deb
dpkg -i pg-oidc-validator-pgdg18.deb
rm pg-oidc-validator-pgdg18.deb

mkdir -p /etc/postgresql/conf.d /docker-entrypoint-initdb.d

cat > /etc/postgresql/conf.d/oauth.conf <<'EOF'
listen_addresses = '*'
oauth_validator_libraries = 'pg_oidc_validator'
pg_oidc_validator.authn_field = 'email'
EOF

cat > /tmp/pg_hba.conf <<'EOF'
host all all 0.0.0.0/0 oauth scope="email pgscope",issuer=https://keycloak:8443/realms/pgrealm,map=emap
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
EOF

cat > /tmp/pg_ident.conf <<'EOF'
emap testuser@example.com testuser
EOF

cat > /docker-entrypoint-initdb.d/create-testuser.sh <<'EOF'
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE ROLE testuser LOGIN SUPERUSER;
EOSQL
EOF

chmod +x /docker-entrypoint-initdb.d/create-testuser.sh

exec docker-entrypoint.sh postgres -c hba_file=/tmp/pg_hba.conf -c ident_file=/tmp/pg_ident.conf -c config_file=/etc/postgresql/conf.d/oauth.conf
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10

psql-client:
image: postgres:18
profiles:
- client
volumes:
- ./certs/crt.pem:/usr/local/share/ca-certificates/keycloak-test.crt:ro
depends_on:
postgres:
condition: service_healthy
entrypoint:
- bash
- -c
- |
apt-get update -qq
apt-get install -y -qq ca-certificates libpq-oauth
update-ca-certificates

echo ''
echo '========================================'
echo 'PostgreSQL OAuth Client'
echo '========================================'
echo ''
echo 'Connecting to PostgreSQL with OAuth...'
echo ''
echo 'When prompted, follow the device flow:'
echo ' 1. Visit the URL shown in your browser'
echo ' 2. Enter the device code'
echo ' 3. Login with: testuser / asdfasdf'
echo ''
echo '========================================'
echo ''

exec psql 'host=postgres dbname=postgres user=testuser oauth_issuer=https://keycloak:8443/realms/pgrealm oauth_client_id=pgtest'
stdin_open: true
tty: true