Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
12 changes: 8 additions & 4 deletions .github/workflows/build_container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ name: Build and publish container main - themed
on: [push, workflow_dispatch]

env:
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }}
# Fall back to a placeholder org on forks that haven't set the
# DOCKER_HUB_ORGANIZATION repo variable, so the image tag stays valid and the
# build still runs (validates) instead of failing with "invalid reference
# format". Publishing is separately gated below on a real org being configured.
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION || 'obp' }}
DOCKER_HUB_REPOSITORY: obp-keycloak

jobs:
Expand All @@ -28,7 +32,7 @@ jobs:
echo Building container image for branch ${{ github.ref }} done
fi
- name: Push container image
if: vars.DEPLOY_CONTAINERS == 'true'
if: vars.DEPLOY_CONTAINERS == 'true' && vars.DOCKER_HUB_ORGANIZATION != ''

run: |
echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io
Expand All @@ -37,11 +41,11 @@ jobs:
- uses: sigstore/cosign-installer@main
- name: Write signing key to disk (only needed for `cosign sign --key`)

if: vars.DEPLOY_CONTAINERS == 'true'
if: vars.DEPLOY_CONTAINERS == 'true' && vars.DOCKER_HUB_ORGANIZATION != ''
run: echo "${{ secrets.COSIGN_PRIVATE_KEY }}" > cosign.key

- name: Sign container image
if: vars.DEPLOY_CONTAINERS == 'true'
if: vars.DEPLOY_CONTAINERS == 'true' && vars.DOCKER_HUB_ORGANIZATION != ''

run: |
cosign sign -y --key cosign.key \
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,29 @@ Click the [User federation](https://localhost:8443/admin/master/console/#/master

![KC providers](/docs/images/providers.png?raw=true "KC providers")

## Identity Provider brokering (e.g. Google OIDC)

Keycloak can also broker external identity providers (Google, etc.) alongside this
federation provider. The two are complementary:

- **User federation** (this provider) sources existing **OBP** users for lookup and
password authentication.
- **Identity providers** let users sign in through an external IdP.

On a user's *first* broker login, Keycloak materializes a local user and links the
external identity to it. This provider is intentionally **not** a
`UserRegistrationProvider`, so Keycloak stores brokered IdP users in **its own
database** rather than attempting to create them in OBP. OBP users remain read-only
(see [Provider Features](#provider-features)); brokered IdP users are managed in
Keycloak.

> If this provider advertised user registration, the first-broker-login flow would
> route user creation to it and fail with `IDENTITY_PROVIDER_FIRST_LOGIN_ERROR`,
> since OBP user creation is not supported from Keycloak.

## Documentation

- [docs/ONBOARDING.md](docs/ONBOARDING.md) — local development onboarding (fresh-start guide)
- [env.sample](env.sample) — full environment variable reference
- [docs/CICD_DEPLOYMENT.md](docs/CICD_DEPLOYMENT.md) — CI/CD deployment guide
- [development/README.md](development/README.md) — development scripts documentation
12 changes: 8 additions & 4 deletions development/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ docker build --no-cache --build-arg THEMED=true -t obp-keycloak:themed -f develo

The unified Dockerfile accepts several build-time arguments (passed with `--build-arg`). The provider JAR must be pre-built on the host before running `docker build` (the deployment script handles this automatically).

- `KEYCLOAK_VERSION` (default: `26.5.1`) — the Keycloak base image tag used in the builder and final images. Example:
- `KEYCLOAK_VERSION` (default: `26.5.3`) — the Keycloak base image tag used in the builder and final images. Example:
```bash
--build-arg KEYCLOAK_VERSION=26.5.1
--build-arg KEYCLOAK_VERSION=26.5.3
```

- `THEMED` (default: `false`) — controls whether the Dockerfile retains the `themes/obp` and `themes/obp-dark` directories in the final image. Set to `true` to keep themes in the image (the deployment script passes this when `--themed` is used):
Expand Down Expand Up @@ -180,12 +180,16 @@ docker rm obp-keycloak-local
docker ps --filter name=obp-keycloak-local
```

> **Tuning log level/format, rotation, and identifying the running build:**
> see [Logging & observability](../docs/ONBOARDING.md#6-logging--observability)
> (`KC_LOG_LEVEL`, `KC_LOG_CONSOLE_OUTPUT`, `DOCKER_LOG_MAX_*`, startup build banner).

---

## Themed Deployments

### Prerequisites
- `themes/obp/theme.properties` - Theme configuration
- `themes/obp/login/theme.properties` - Theme configuration (Keycloak reads the per-type file)
- `themes/obp/login/login.ftl` - Login template
- `themes/obp/login/template.ftl` - Base template
- Optional: CSS, images, message files in `themes/obp/login/resources/`
Expand Down Expand Up @@ -231,7 +235,7 @@ docker system prune -f
find themes/obp -type f

# Verify theme configuration
cat themes/obp/theme.properties
cat themes/obp/login/theme.properties

# Try standard deployment first
./development/run-local-postgres-cicd.sh
Expand Down
9 changes: 8 additions & 1 deletion development/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Development Dockerfile for OBP Keycloak Provider
#
# Usage:
# docker build --build-arg KEYCLOAK_VERSION=26.5.1 -t obp-keycloak:latest .
# docker build --build-arg KEYCLOAK_VERSION=26.5.3 -t obp-keycloak:latest .
#
# Notes:
# - The provider JAR must be pre-built on the host before running docker build
Expand All @@ -22,6 +22,10 @@ ARG JAR_CHECKSUM
# === Keycloak Builder Stage ===
FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} as builder

# Tag every image layer this project produces so the deploy script can prune
# only OUR dangling leftovers (label-filtered) without touching other projects.
LABEL com.openbankproject.image=obp-keycloak-provider

# Bring build args into this stage for cache invalidation metadata
ARG BUILD_TIMESTAMP
ARG JAR_CHECKSUM
Expand Down Expand Up @@ -56,6 +60,9 @@ RUN /opt/keycloak/bin/kc.sh build
# === Final Runtime Image ===
FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}

# Same project label on the final image (and thus on prior orphaned builds).
LABEL com.openbankproject.image=obp-keycloak-provider

# Bring the prebuilt Keycloak server into the final image
COPY --from=builder /opt/keycloak/ /opt/keycloak/
COPY --from=builder /tmp/build-info.txt /opt/keycloak/
Expand Down
9 changes: 9 additions & 0 deletions development/docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
# Custom entrypoint wrapper for OBP Keycloak Provider
# Injects runtime environment variables into theme configuration before starting Keycloak.

# --- Build/version banner ---
# Surface which build is running so logs can be correlated to a specific image
# during deployment/maintenance (build-info.txt is baked in at image build time).
if [ -f /opt/keycloak/build-info.txt ]; then
echo "[OBP Entrypoint] ===== OBP Keycloak Provider build ====="
sed 's/^/[OBP Entrypoint] /' /opt/keycloak/build-info.txt
echo "[OBP Entrypoint] ======================================="
fi

# --- Forgot Password URL override ---
# If FORGOT_PASSWORD_URL is set, inject it into all theme.properties files
# so the "Forgot Password?" link points to a custom URL instead of Keycloak's default.
Expand Down
10 changes: 5 additions & 5 deletions development/docs/themed-keycloak-design-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This document describes the end-to-end process for building and running a locall
| Host Machine | | Docker Container | | PostgreSQL (host) |
| | | (obp-keycloak-local) | | |
| .env | | +-------------------------+ | | keycloakdb |
| themes/obp/ | -----> | | Keycloak 26.5.1 | | <----> | (realm data, |
| themes/obp/ | -----> | | Keycloak 26.5.3 | | <----> | (realm data, |
| themes/obp-dark/ | build | | + obp-keycloak- | | JDBC | clients, tokens) |
| src/ + pom.xml | | | provider.jar | | +---------------------+
| | | | + obp theme | |
Expand Down Expand Up @@ -82,7 +82,7 @@ The deployment script executes an 8-step pipeline:
- `OBP_AUTHUSER_PROVIDER` (mandatory provider filter)
- **Themed-specific**: runs `validate_theme_files()` which verifies:
- `themes/obp/` directory exists
- `themes/obp/theme.properties` contains `parent=base` and `styles=`
- `themes/obp/login/theme.properties` contains `parent=base` and `styles=`
- `themes/obp/login/` exists with `login.ftl` and `template.ftl`
- Optionally checks for CSS, images, and i18n message files

Expand Down Expand Up @@ -122,7 +122,7 @@ docker build --no-cache \
#### Dockerfile Multi-Stage Build

```
Stage 1: builder (quay.io/keycloak/keycloak:26.5.1)
Stage 1: builder (quay.io/keycloak/keycloak:26.5.3)
+--------------------------------------------------+
| Generate self-signed SSL keystore |
| COPY obp-keycloak-provider.jar -> providers/ |
Expand All @@ -131,7 +131,7 @@ docker build --no-cache \
+--------------------------------------------------+
|
v
Stage 2: final (quay.io/keycloak/keycloak:26.5.1)
Stage 2: final (quay.io/keycloak/keycloak:26.5.3)
+--------------------------------------------------+
| COPY --from=builder /opt/keycloak/ |
| COPY themes/obp/ -> themes/obp/ |
Expand Down Expand Up @@ -162,7 +162,7 @@ The container is launched with:
- Polls `https://<host>:<mgmt_port>/health/ready` every 2 seconds, up to 120 seconds.
- **Themed-specific** post-readiness checks:
- Tests if theme resources are accessible at `/resources/obp/`
- Verifies theme files exist inside the container at `/opt/keycloak/themes/obp/theme.properties`
- Verifies theme files exist inside the container at `/opt/keycloak/themes/obp/login/theme.properties`

---

Expand Down
70 changes: 68 additions & 2 deletions development/run-local-postgres-cicd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,53 @@ echo -e "${GREEN}✓ Connectivity check done${NC}"
# Step 3: Clean build
echo -e "${CYAN}[3/8] Building Maven Project${NC}"

mvn clean package -DskipTests -q
# The provider targets Java 17 (see pom.xml maven.compiler.source/target). If the
# shell default is an older JDK, the build fails with "invalid target release: 17".
# Auto-select a Java 17 JDK unless the active java is already >= 17.
ensure_java17() {
local current_major=""
if command -v java &> /dev/null; then
# "openjdk version "17.0.1"" → 17, or "11.0.30" → 11
current_major=$(java -version 2>&1 | sed -n 's/.*version "\([0-9]*\).*/\1/p' | head -1)
fi

if [ -n "$current_major" ] && [ "$current_major" -ge 17 ] 2>/dev/null; then
return 0
fi

# Look for a Java 17+ JDK in common locations
local candidate
for candidate in \
"$JAVA_HOME" \
/usr/lib/jvm/java-17-openjdk-amd64 \
/usr/lib/jvm/java-1.17.0-openjdk-amd64 \
/usr/lib/jvm/java-21-openjdk-amd64 \
/usr/lib/jvm/temurin-17-jdk-amd64 \
/usr/lib/jvm/java-17-openjdk; do
if [ -n "$candidate" ] && [ -x "$candidate/bin/java" ]; then
local cand_major
cand_major=$("$candidate/bin/java" -version 2>&1 | sed -n 's/.*version "\([0-9]*\).*/\1/p' | head -1)
if [ -n "$cand_major" ] && [ "$cand_major" -ge 17 ] 2>/dev/null; then
export JAVA_HOME="$candidate"
export PATH="$candidate/bin:$PATH"
echo -e "${BLUE} Using Java $cand_major for the build: $candidate${NC}"
return 0
fi
fi
done

echo -e "${RED}✗ Java 17+ is required to build the provider, but none was found${NC}"
echo " Active java: ${current_major:-unknown}. Install a JDK 17 (e.g. apt install openjdk-17-jdk)"
echo " or set JAVA_HOME to a Java 17+ installation before running this script."
exit 1
}

ensure_java17

# Compile the provider against the same Keycloak version the image runs
# (KEYCLOAK_VERSION from .env), overriding the pom.xml default so the SPI
# matches the runtime exactly.
mvn clean package -DskipTests -q -Dversion.keycloak="$KEYCLOAK_VERSION"

if [ $? -ne 0 ]; then
echo -e "${RED}✗ Maven build failed${NC}"
Expand Down Expand Up @@ -239,6 +285,15 @@ fi
rm -f "$DOCKER_BUILD_LOG"
echo -e "${GREEN}✓ Docker image built${NC}"

# Reclaim build leftovers: every --no-cache rebuild orphans the previous image
# (and intermediate stages) into dangling images. Prune only images carrying this
# project's label so other projects' images and build cache are never touched.
PRUNE_LABEL="com.openbankproject.image=obp-keycloak-provider"
RECLAIMED=$(docker image prune -f --filter "label=$PRUNE_LABEL" 2>/dev/null | grep -i 'reclaimed' || true)
if [ -n "$RECLAIMED" ]; then
echo -e "${BLUE} Reclaimed build leftovers (${PRUNE_LABEL}): ${RECLAIMED##*Total reclaimed space: }${NC}"
fi

# Step 7: Start new container
echo -e "${CYAN}[7/8] Starting New Container${NC}"

Expand Down Expand Up @@ -276,13 +331,24 @@ CONTAINER_ENV_VARS=(
"-e" "KC_METRICS_ENABLED=${KC_METRICS_ENABLED:-true}"
"-e" "KC_FEATURES=${KC_FEATURES:-token-exchange}"
"-e" "FORGOT_PASSWORD_URL=${FORGOT_PASSWORD_URL:-}"
# Logging — tune verbosity without rebuilding, and pick console format.
# KC_LOG_LEVEL root log level (e.g. INFO, DEBUG, WARN); category
# overrides allowed, e.g. "INFO,io.tesobe:DEBUG"
# KC_LOG_CONSOLE_OUTPUT 'default' (human text) or 'json' (for log aggregators)
"-e" "KC_LOG_LEVEL=${KC_LOG_LEVEL:-INFO}"
"-e" "KC_LOG_CONSOLE_OUTPUT=${KC_LOG_CONSOLE_OUTPUT:-default}"
)

# Start container with host networking — no port mapping needed, Keycloak binds
# directly to host ports (HTTP: ${KEYCLOAK_HTTP_PORT:-7787}, HTTPS: 8443, mgmt: 9000)
#
# Cap the json-file log so it can't grow unbounded and fill the disk on a
# long-lived host (override via DOCKER_LOG_MAX_SIZE / DOCKER_LOG_MAX_FILE).
docker run -d \
--name "$CONTAINER_NAME" \
--network host \
--log-opt max-size="${DOCKER_LOG_MAX_SIZE:-10m}" \
--log-opt max-file="${DOCKER_LOG_MAX_FILE:-5}" \
"${CONTAINER_ENV_VARS[@]}" \
"$IMAGE_TAG" > /dev/null 2>&1

Expand All @@ -306,7 +372,7 @@ while [ $WAIT_COUNT -lt $MAX_WAIT ] && [ "$READY" = false ]; do
echo -e "${GREEN}✓ Service is ready${NC}"

echo -n "Verifying theme installation... "
if docker exec "$CONTAINER_NAME" ls /opt/keycloak/themes/obp/theme.properties > /dev/null 2>&1; then
if docker exec "$CONTAINER_NAME" ls /opt/keycloak/themes/obp/login/theme.properties > /dev/null 2>&1; then
echo -e "${GREEN}✓ Theme files installed${NC}"
else
echo -e "${RED}✗ Theme files missing in container${NC}"
Expand Down
2 changes: 1 addition & 1 deletion docs/CICD_DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ The script follows an 8-step pipeline:
## Theme Validation (--themed flag)

**Themed Deployment Requirements**:
- ✅ `themes/obp/theme.properties` with valid content
- ✅ `themes/obp/login/theme.properties` with valid content
- ✅ `themes/obp/login/` directory structure
- ✅ Required templates: `login.ftl`, `template.ftl`
- ✅ Optional: CSS files, images, message files
Expand Down
Loading