From f822917d8af0b7c6ccfb1dbe1ee7df270b3c3065 Mon Sep 17 00:00:00 2001 From: "@tanya_r" Date: Fri, 19 Jun 2026 18:42:01 -0300 Subject: [PATCH] feat(sandbox): add keycloak to the sandbox stack with an imported demo realm Give the sandbox a real OAuth2 issuer so the token-protected ledger and payment journeys are exercisable end to end. Keycloak boots from a static realm import with a client-credentials demo client whose default scopes match the services' required authorities, and its hostname is pinned so every issued token carries the in-network issuer the services validate, whether the token is requested from inside the network or from the host. The ledger and payments services point at that issuer. The compose smoke now proves the whole auth chain: it fetches a token and calls a protected ledger and payments endpoint expecting success. A path-scoped gitleaks allowlist covers the documented sandbox demo secret. Closes #315 --- .github/workflows/ci.yml | 5 ++- .gitleaks.toml | 10 +++++ deploy/keycloak/fincore-realm.json | 71 ++++++++++++++++++++++++++++++ docker-compose.yml | 32 +++++++++++++- scripts/demo-auth.sh | 46 +++++++++++++++++++ 5 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 .gitleaks.toml create mode 100644 deploy/keycloak/fincore-realm.json create mode 100644 scripts/demo-auth.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8719d2..38f3e97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,13 +149,16 @@ jobs: uses: actions/checkout@v4 - name: Bring up stack - run: docker compose up -d --build --wait --wait-timeout 90 + run: docker compose up -d --build --wait --wait-timeout 180 - name: Smoke (readiness < 30s, public surface served) run: | bash scripts/demo.sh http://localhost:8080 bash scripts/demo.sh http://localhost:8081 + - name: Auth chain proof (token -> protected endpoint -> 200) + run: bash scripts/demo-auth.sh + - name: Dump container logs on failure if: failure() run: docker compose logs --no-color diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..1e7483b --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,10 @@ +# Extends the default gitleaks ruleset; only adds a path-scoped allowlist. +[extend] +useDefault = true + +[allowlist] +description = "Sandbox demo Keycloak realm + token-proof script: a documented, intentionally public demo client secret, not a real credential" +paths = [ + '''^deploy/keycloak/fincore-realm\.json$''', + '''^scripts/demo-auth\.sh$''', +] diff --git a/deploy/keycloak/fincore-realm.json b/deploy/keycloak/fincore-realm.json new file mode 100644 index 0000000..87b761d --- /dev/null +++ b/deploy/keycloak/fincore-realm.json @@ -0,0 +1,71 @@ +{ + "realm": "fincore", + "enabled": true, + "accessTokenLifespan": 300, + "sslRequired": "none", + "clientScopes": [ + { + "name": "ledger:read", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + }, + { + "name": "ledger:write", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + }, + { + "name": "payments:read", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + }, + { + "name": "payments:write", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + }, + { + "name": "decision:read", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + }, + { + "name": "decision:write", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + }, + { + "name": "compliance:read", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + }, + { + "name": "compliance:write", + "protocol": "openid-connect", + "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "false" } + } + ], + "clients": [ + { + "clientId": "fincore-demo", + "enabled": true, + "protocol": "openid-connect", + "publicClient": false, + "secret": "fincore-demo-secret", + "serviceAccountsEnabled": true, + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "defaultClientScopes": [ + "ledger:read", + "ledger:write", + "payments:read", + "payments:write", + "decision:read", + "decision:write", + "compliance:read", + "compliance:write" + ], + "optionalClientScopes": [] + } + ] +} diff --git a/docker-compose.yml b/docker-compose.yml index f75264b..234a8ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,18 +14,44 @@ services: retries: 10 start_period: 5s + keycloak: + image: quay.io/keycloak/keycloak:26.6.1 + command: ["start-dev", "--import-realm"] + environment: + KC_BOOTSTRAP_ADMIN_USERNAME: admin + KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin} + KC_HOSTNAME: http://keycloak:8080 + KC_HTTP_ENABLED: "true" + KC_HEALTH_ENABLED: "true" + ports: + - "8085:8080" + volumes: + - ./deploy/keycloak:/opt/keycloak/data/import:ro + healthcheck: + test: + [ + "CMD-SHELL", + "exec 3<>/dev/tcp/localhost/9000; echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3; cat <&3 | grep -q 'HTTP/1.1 200'", + ] + interval: 5s + timeout: 5s + retries: 30 + start_period: 30s + ledger: build: . depends_on: postgres: condition: service_healthy + keycloak: + condition: service_started ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/ledger SPRING_DATASOURCE_USERNAME: ledger SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-ledger} - KEYCLOAK_ISSUER_URI: http://localhost/realms/fincore + KEYCLOAK_ISSUER_URI: http://keycloak:8080/realms/fincore SPRING_LIQUIBASE_CONTEXTS: production,demo OTLP_TRACING_ENDPOINT: http://otel-collector:4318/v1/traces @@ -37,13 +63,15 @@ services: depends_on: postgres: condition: service_healthy + keycloak: + condition: service_started ports: - "8081:8080" environment: SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/payments SPRING_DATASOURCE_USERNAME: ledger SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-ledger} - KEYCLOAK_ISSUER_URI: http://localhost/realms/fincore + KEYCLOAK_ISSUER_URI: http://keycloak:8080/realms/fincore FINCORE_PAYMENTS_BANK_SANDBOX_ENABLED: "true" OTLP_TRACING_ENDPOINT: http://otel-collector:4318/v1/traces diff --git a/scripts/demo-auth.sh b/scripts/demo-auth.sh new file mode 100644 index 0000000..1ce07bf --- /dev/null +++ b/scripts/demo-auth.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BUSL-1.1 +# SPDX-FileCopyrightText: 2026 FinCore Engine Authors +set -euo pipefail + +KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:8085}" +LEDGER_URL="${LEDGER_URL:-http://localhost:8080}" +PAYMENTS_URL="${PAYMENTS_URL:-http://localhost:8081}" +CLIENT_ID="${FINCORE_DEMO_CLIENT_ID:-fincore-demo}" +CLIENT_SECRET="${FINCORE_DEMO_CLIENT_SECRET:-fincore-demo-secret}" +TOKEN_ENDPOINT="$KEYCLOAK_URL/realms/fincore/protocol/openid-connect/token" + +fetch_token() { + curl -fsS -X POST "$TOKEN_ENDPOINT" \ + -d grant_type=client_credentials \ + -d "client_id=$CLIENT_ID" \ + -d "client_secret=$CLIENT_SECRET" | + sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p' +} + +token="" +for attempt in $(seq 1 5); do + token=$(fetch_token || true) + [ -n "$token" ] && break + echo "token fetch attempt $attempt failed; retrying in 2s" + sleep 2 +done + +if [ -z "$token" ]; then + echo "FAIL: could not obtain a client_credentials token from $TOKEN_ENDPOINT" + exit 1 +fi + +ledger_code=$(curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $token" "$LEDGER_URL/v1/accounts") +if [ "$ledger_code" != "200" ]; then + echo "FAIL: GET /v1/accounts with a bearer returned $ledger_code (expected 200)" + exit 1 +fi + +payments_code=$(curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $token" "$PAYMENTS_URL/v1/payments") +if [ "$payments_code" != "200" ]; then + echo "FAIL: GET /v1/payments with a bearer returned $payments_code (expected 200)" + exit 1 +fi + +echo "auth chain OK: client_credentials token accepted by ledger and payments"