|
2 | 2 |
|
3 | 3 | # PostgreSQL OAuth Validator for Keycloak |
4 | 4 |
|
5 | | -TODO |
| 5 | +**Requires**: PostgreSQL 18+ |
6 | 6 |
|
| 7 | +This module enables PostgreSQL 18 to delegate authorization decisions to Keycloak using OAuth tokens, leveraging Keycloak Authorization Services for fine-grained, token-based access control. |
| 8 | +It sends a permission request to Keycloak's token endpoint using `grant_type=urn:ietf:params:oauth:grant-type:uma-ticket` and expects a decision response (`response_mode=decision`), which is a Keycloak-specific extension. |
| 9 | +It is designed for use with CloudNativePG, allowing database role elevation to be controlled by Keycloak policies. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Features |
| 14 | + |
| 15 | +- **Keycloak-based authorization for PostgreSQL roles** |
| 16 | + - Delegates database role elevation decisions to Keycloak Authorization Services using OAuth tokens. |
| 17 | +- **Permission string construction** |
| 18 | + - Builds permission strings as `<resource_name>#<scope>` and sends permission requests to Keycloak's token endpoint (`grant_type=urn:ietf:params:oauth:grant-type:uma-ticket`, `response_mode=decision`). |
| 19 | +- **Configurable via PostgreSQL GUC parameters** |
| 20 | + - All integration settings (endpoints, resource names, timeouts, debug, etc.) are controlled via GUCs. |
| 21 | +- **Secure HTTP communication** |
| 22 | + - Uses libcurl for HTTP requests with configurable timeouts and safe logging. |
| 23 | +- **Optional JWT issuer verification** |
| 24 | + - Can verify the `iss` claim in JWT tokens for additional security. |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## Example: CloudNativePG Configuration |
| 29 | + |
| 30 | +```yaml |
| 31 | +apiVersion: postgresql.cnpg.io/v1 |
| 32 | +kind: Cluster |
| 33 | +metadata: |
| 34 | + name: pg-oauth |
| 35 | +spec: |
| 36 | + imageName: pg18-kc-validator:18.0 # Image containing kc_validator.so |
| 37 | + instances: 1 |
| 38 | + |
| 39 | + postgresql: |
| 40 | + parameters: |
| 41 | + oauth_validator_libraries: "kc_validator" |
| 42 | + kc.token_endpoint: "https://<keycloak>/realms/<realm>/protocol/openid-connect/token" |
| 43 | + kc.audience: "postgres-resource" |
| 44 | + kc.resource_name: "appdb" # Resource name in Keycloak |
| 45 | + kc.client_id: "postgres-resource" |
| 46 | + kc.http_timeout_ms: "2000" |
| 47 | + kc.expected_issuer: "https://<keycloak>/realms/<realm>" |
| 48 | + kc.debug: "on" |
| 49 | + kc.log_body: "on" |
| 50 | + log_min_messages: "debug1" |
| 51 | + pg_hba: |
| 52 | + - host all all 0.0.0.0/0 oauth issuer="https://<keycloak>/realms/<realm>" scope=db_access validator="kc_validator" delegate_ident_mapping=1 |
| 53 | +``` |
| 54 | +
|
| 55 | +For a full example, see `examples/cnpg/cluster.yaml`. |
| 56 | + |
| 57 | +--- |
| 58 | + |
| 59 | +## Keycloak Configuration Steps |
| 60 | + |
| 61 | +1. **Realm** |
| 62 | + Create or use an existing realm (e.g., `demo`). |
| 63 | + |
| 64 | +2. **Resource Server Client** (`kc.audience`) |
| 65 | + Create a client for Authorization Services (e.g., `postgres-resource`). |
| 66 | + Enable Authorization Services and add scopes as needed (e.g., `app_readonly`, `app_readwrite`). |
| 67 | + |
| 68 | +3. **Validator Client** (`kc.client_id`) |
| 69 | + A client allowed to call the token endpoint for permission decisions. |
| 70 | + |
| 71 | +4. **Resource & Permission** |
| 72 | + Resource name: `<kc.resource_name>` (e.g., `appdb`). |
| 73 | + Scope name: `<scope>` (e.g., `app_readonly`, `app_readwrite`). |
| 74 | + Permission name: `<resource_name>#<scope>` (e.g., `appdb#app_readonly`). |
| 75 | + Create a permission for each database role you want to allow (e.g., DB role `app_readonly` maps to Keycloak scope `app_readonly`, permission name `appdb#app_readonly`). |
| 76 | + |
| 77 | +5. **Policies** |
| 78 | + Attach policies to permissions so that only intended users can access specific scopes. |
| 79 | + |
| 80 | +6. **Issuer Verification (optional)** |
| 81 | + Set `kc.expected_issuer` to your realm's issuer URL (e.g., `https://<keycloak>/realms/<realm>`). |
| 82 | + |
| 83 | +--- |
| 84 | + |
| 85 | +## Quick Start with psql and Device Flow |
| 86 | + |
| 87 | +You can quickly test the validator using Keycloak's Device Flow and psql: |
| 88 | + |
| 89 | +1. **Connect to PostgreSQL using psql with OAuth parameters:** |
| 90 | + |
| 91 | + ```bash |
| 92 | + psql "host=<keycloak> \ |
| 93 | + user=app_readonly \ |
| 94 | + dbname=appdb \ |
| 95 | + oauth_issuer=https://<keycloak>/realms/demo \ |
| 96 | + oauth_client_id=appA \ |
| 97 | + oauth_client_secret=<client secret> \ |
| 98 | + oauth_scope='db_access'" |
| 99 | + ``` |
| 100 | + |
| 101 | + When you run this command, psql will display a Device Authorization URL and a device code. |
| 102 | + |
| 103 | +2. **Authenticate via browser:** |
| 104 | + |
| 105 | + - Open the displayed URL in your browser. |
| 106 | + - Enter the device code shown by psql. |
| 107 | + - Log in with your Keycloak username and password. |
| 108 | + |
| 109 | + Once authentication is complete, psql will automatically obtain an access token and connect to the database. |
| 110 | + |
| 111 | +> Note: |
| 112 | +The DB role (`app_readonly`) should match the Keycloak scope name. |
| 113 | +The validator will request permission `<resource_name>#<scope>` (e.g., `appdb#app_readonly`) from Keycloak Authorization Services. |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## Build Instructions |
| 118 | + |
| 119 | +### Docker |
| 120 | + |
| 121 | +```bash |
| 122 | +docker build -t pg-kc-validator -f docker/Dockerfile . |
| 123 | +``` |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## Security Notes |
| 128 | + |
| 129 | +- Do not use self-signed certificates (server.crt) in production; always use a trusted CA. |
| 130 | +- Enable `kc.log_body` only for debugging; keep it `off` in production. |
| 131 | +- Place CA certificates in `/usr/local/share/ca-certificates/` and run `update-ca-certificates` in your Docker image. |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## License |
| 136 | + |
| 137 | +Apache-2.0. See `LICENSE`. |
| 138 | + |
| 139 | +--- |
0 commit comments