From a33bda6f2a6a03f5954c83fc9637660b0bc16e95 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Thu, 13 Nov 2025 20:53:44 +0100 Subject: [PATCH] Add a sample docker-compose configuration This is just a simple setup that users can try out without setting up anything manually, if they have docker installed. --- README.md | 5 ++ examples/keycloak/README.md | 54 ++++++++++++ examples/keycloak/certs/crt.pem | 34 ++++++++ examples/keycloak/certs/gencert.sh | 6 ++ examples/keycloak/certs/key.pem | 52 ++++++++++++ examples/keycloak/docker-compose.yml | 121 +++++++++++++++++++++++++++ 6 files changed, 272 insertions(+) create mode 100644 examples/keycloak/README.md create mode 100644 examples/keycloak/certs/crt.pem create mode 100755 examples/keycloak/certs/gencert.sh create mode 100644 examples/keycloak/certs/key.pem create mode 100644 examples/keycloak/docker-compose.yml diff --git a/README.md b/README.md index c4587db..ed4508d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/examples/keycloak/README.md b/examples/keycloak/README.md new file mode 100644 index 0000000..9013453 --- /dev/null +++ b/examples/keycloak/README.md @@ -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` diff --git a/examples/keycloak/certs/crt.pem b/examples/keycloak/certs/crt.pem new file mode 100644 index 0000000..4a66b25 --- /dev/null +++ b/examples/keycloak/certs/crt.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/examples/keycloak/certs/gencert.sh b/examples/keycloak/certs/gencert.sh new file mode 100755 index 0000000..3c196e3 --- /dev/null +++ b/examples/keycloak/certs/gencert.sh @@ -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" diff --git a/examples/keycloak/certs/key.pem b/examples/keycloak/certs/key.pem new file mode 100644 index 0000000..7d16ce5 --- /dev/null +++ b/examples/keycloak/certs/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +Z0chEXWiWdvIrJ0PVV304gW1cdzvQQ== +-----END PRIVATE KEY----- diff --git a/examples/keycloak/docker-compose.yml b/examples/keycloak/docker-compose.yml new file mode 100644 index 0000000..9a66eb1 --- /dev/null +++ b/examples/keycloak/docker-compose.yml @@ -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