From 09ab67b4ab991255dd201f7bac9764a01ab0a16a Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Fri, 26 Jun 2026 11:39:24 -0400 Subject: [PATCH 1/9] WIP --- .gitignore | 1 + docker-compose.yml | 103 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index be318a5..29110d7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .bundle/* ./local/* .DS_Store +.env /data/* !/data/gingr diff --git a/docker-compose.yml b/docker-compose.yml index 4f0e4dc..e69bfea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,8 @@ services: condition: service_healthy geoserver-secure: condition: service_healthy + keycloak-config: + condition: service_completed_successfully command: - tail - -f @@ -25,7 +27,7 @@ services: SPATIAL_URL: http://spatial restart: no volumes: - - ./:/opt/app:delegated + - ./:/opt/app solr: command: > @@ -51,27 +53,118 @@ services: - ./data/spatial:/usr/local/apache2/htdocs/:ro geoserver: - image: ghcr.io/berkeleylibrary/geoserver:latest + image: docker.osgeo.org/geoserver:2.28-latest ports: - 8080:8080 + environment: &geoserver-environment + ROOT_WEBAPP_REDIRECT: "true" + SKIP_DEMO_DATA: "true" + INSTALL_EXTENSIONS: "true" + COMMUNITY_EXTENSIONS: "sec-oidc" + depends_on: + keycloak-config: + condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/geoserver/web/"] interval: 15s timeout: 10s retries: 10 start_period: 30s + extra_hosts: + - keycloak.localhost:host-gateway volumes: - - ./data/geoserver/public:/srv/geofiles:delegated + - ./data/geoserver/geoserver_data:/opt/geoserver_data + - ./data/geoserver/public:/srv/geofiles geoserver-secure: - image: ghcr.io/berkeleylibrary/geoserver:latest + image: docker.osgeo.org/geoserver:2.28-latest ports: - 8081:8080 + environment: *geoserver-environment + depends_on: + keycloak-config: + condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/geoserver/web/"] interval: 15s timeout: 10s retries: 10 start_period: 30s + extra_hosts: + - keycloak.localhost:host-gateway + volumes: + - ./data/geoserver/geoserver_secure_data:/opt/geoserver_data + - ./data/geoserver/UCB:/srv/geofiles + + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: keycloak + healthcheck: + test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"] + interval: 10s + timeout: 5s + retries: 5 + volumes: + - postgres_data:/var/lib/postgresql/data + + keycloak: + command: start-dev + depends_on: + postgres: + condition: service_healthy + environment: + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak + KC_DB_USERNAME: keycloak + KC_DB_PASSWORD: keycloak + KC_BOOTSTRAP_ADMIN_USERNAME: admin + KC_BOOTSTRAP_ADMIN_PASSWORD: admin + KC_HOSTNAME: "http://keycloak.localhost:8180" + KC_HOSTNAME_STRICT: "false" + KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true" + KC_HTTP_ENABLED: "true" + KC_HTTP_PORT: "8180" + KC_HEALTH_ENABLED: "true" + KC_LOG_LEVEL: ${KC_LOG_LEVEL:-INFO} + healthcheck: + # curl/wget are not present in the UBI9-based Keycloak image; use /dev/tcp. + # KC_HEALTH_ENABLED=true exposes /health/ready on the management port (9000). + test: + [ + "CMD-SHELL", + "exec 3<>/dev/tcp/localhost/9000 && printf 'GET /health/ready HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n' >&3 && grep -q 'status.*UP' <&3", + ] + interval: 30s + timeout: 10s + retries: 5 + start_period: 20s + image: quay.io/keycloak/keycloak:26.1 + ports: + - 127.0.0.1:8180:8180 + restart: always + + keycloak-config: + depends_on: + keycloak: + condition: service_healthy + environment: + KEYCLOAK_URL: http://keycloak:8180 + KEYCLOAK_USER: admin + KEYCLOAK_PASSWORD: admin + KEYCLOAK_AVAILABILITYCHECK_ENABLED: "true" + IMPORT_FILES_LOCATIONS: /config/* + IMPORT_VAR_SUBSTITUTION_ENABLED: "true" + OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} + OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} + OIDC_ADMIN_GROUP: ${OIDC_ADMIN_GROUP} + OIDC_USER_GROUP: ${OIDC_USER_GROUP} + image: adorsys/keycloak-config-cli:latest-26.1.0 + restart: on-failure:3 volumes: - - ./data/geoserver/UCB:/srv/geofiles:delegated + - ./data/geoserver/keycloak/config:/config + +volumes: + postgres_data: From 5e9a4b8c22da439a0637ad594c5486cd1086814c Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Mon, 6 Jul 2026 11:52:12 -0400 Subject: [PATCH 2/9] setup oidc for local dev on geoserver --- .gitignore | 7 ++ README.md | 6 ++ bin/add-keycloak-to-geoserver.sh | 29 +++++ .../geoserver_oidc_seed/security/config.xml | 46 ++++++++ .../security/filter/keycloak/config.xml | 42 ++++++++ .../keycloak/config/berkeley-local.yaml | 102 ++++++++++++++++++ docker-compose.yml | 10 +- env.example | 6 ++ 8 files changed, 246 insertions(+), 2 deletions(-) create mode 100755 bin/add-keycloak-to-geoserver.sh create mode 100644 data/geoserver/geoserver_oidc_seed/security/config.xml create mode 100644 data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml create mode 100644 data/geoserver/keycloak/config/berkeley-local.yaml create mode 100644 env.example diff --git a/.gitignore b/.gitignore index 29110d7..c75fea4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,14 @@ /data/gingr/processed/* /data/gingr/failed/* /data/gingr/processing/* +!/data/geoserver/ +/data/geoserver/* +!/data/geoserver/geoserver_oidc_seed/ +!/data/geoserver/keycloak/ +# !data/geoserver/geoserver_oidc_seed/security/config.xml +# !data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml +# !data/geoserver/keycloak/config/berkeley-local.yaml /solr/geodata-test/data /solr/geodata-test/data diff --git a/README.md b/README.md index cc4b415..10dda75 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,15 @@ Command-line tool for ingesting GeoData (Solr, GeoServer, and file servers). The app is Dockerized with an image designed to just run the `gingr` executable: ```sh +# set your env and then make any changes necessary. +cp .env.example .env + # Build the image docker compose build +# First time you run locally, you need to seed the OIDC config files and restart geoserver. +docker compose up -d && ./bin/add-keycloak-to-geoserver.sh + # Use `docker compose watch` to automatically rebuild on changes. This takes over the terminal # you run it in, so you'll need to either background it (`screen` works great) or open a different window. docker compose watch diff --git a/bin/add-keycloak-to-geoserver.sh b/bin/add-keycloak-to-geoserver.sh new file mode 100755 index 0000000..95dc8a3 --- /dev/null +++ b/bin/add-keycloak-to-geoserver.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +HEALTH_URL="${GEOSERVER_URL:-http://localhost:8080/geoserver/web/}" +WAIT_ATTEMPTS="${WAIT_ATTEMPTS:-60}" + +echo "Waiting for geoserver to be healthy..." +for ((attempt = 1; attempt <= WAIT_ATTEMPTS; attempt++)); do + if curl -fsS "$HEALTH_URL" >/dev/null 2>&1; then + echo "GeoServer is healthy (attempt $attempt)" + break + fi + if [[ $attempt -eq $WAIT_ATTEMPTS ]]; then + echo "ERROR: GeoServer did not become healthy after $WAIT_ATTEMPTS attempts" >&2 + exit 1 + fi + echo " attempt $attempt/$WAIT_ATTEMPTS — not ready yet, waiting 10s..." + sleep 10 +done + +echo "Copying OIDC seed config files..." +docker compose exec geoserver mkdir -p /opt/geoserver_data/security/filter/keycloak +docker compose exec geoserver cp /opt/seed/security/config.xml /opt/geoserver_data/security/config.xml +docker compose exec geoserver cp /opt/seed/security/filter/keycloak/config.xml /opt/geoserver_data/security/filter/keycloak/config.xml + +echo "Restarting geoserver..." +docker compose restart geoserver + +echo "Done." \ No newline at end of file diff --git a/data/geoserver/geoserver_oidc_seed/security/config.xml b/data/geoserver/geoserver_oidc_seed/security/config.xml new file mode 100644 index 0000000..9658cf1 --- /dev/null +++ b/data/geoserver/geoserver_oidc_seed/security/config.xml @@ -0,0 +1,46 @@ + + default + + default + + pbePasswordEncoder + false + + + rememberme + form + keycloak + anonymous + + + form + + + formLogout + + + basic + anonymous + + + basic + + + basic + anonymous + + + + org.geoserver.security.rememberme.GeoServerTokenBasedRememberMeServices + geoserver + + + true + 1 + 5 + 100 + + 127.0.0.1 + + + \ No newline at end of file diff --git a/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml b/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml new file mode 100644 index 0000000..07b9df6 --- /dev/null +++ b/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml @@ -0,0 +1,42 @@ + + -5c6b9fa7:19f233f931b:-7fe3 + keycloak + org.geoserver.security.oauth2.login.GeoServerOAuth2LoginAuthenticationFilter + IdToken + http://localhost:8080/geoserver/ + false + email + http://localhost:8080/geoserver/web/login/oauth2/code/google + false + id + http://localhost:8080/geoserver/web/login/oauth2/code/gitHub + false + sub + http://localhost:8080/geoserver/web/login/oauth2/code/microsoft + openid profile email + true + geoserver-local + change-me + email + http://localhost:8080/geoserver/web/login/oauth2/code/oidc + openid berkeley_edu_groups email profile + http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/token + http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/auth + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/userinfo + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/certs + http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/logout + false + false + true + false + false + false + preferred_username + http://localhost:8080/geoserver/web/ + true + false + false + testadmin=ADMIN + false + \ No newline at end of file diff --git a/data/geoserver/keycloak/config/berkeley-local.yaml b/data/geoserver/keycloak/config/berkeley-local.yaml new file mode 100644 index 0000000..2269a3a --- /dev/null +++ b/data/geoserver/keycloak/config/berkeley-local.yaml @@ -0,0 +1,102 @@ +# keycloak-config-cli realm configuration for local GeoServer development. +# It provides a local Keycloak realm, GeoServer client, and Berkeley-style +# group claims for predictable OIDC testing. +# +# Group names mirror the production LDAP DN format so GeoServer can test the same +# role-mapping shape used against the real IdP. +# +# Secrets are injected at import time via substitution by keycloak-config-cli. +# Set OIDC_CLIENT_SECRET in your .env file — never commit a real secret here. + +realm: berkeley-local +enabled: true +displayName: Berkeley Local + +clientScopes: + - name: berkeley_edu_groups + description: Berkeley group memberships + protocol: openid-connect + attributes: + include.in.token.scope: "true" + display.on.consent.screen: "false" + protocolMappers: + - name: groups + protocol: openid-connect + protocolMapper: oidc-group-membership-mapper + consentRequired: false + config: + # Match Calnet's behavior by not prepending groups with "/" + full.path: "false" + id.token.claim: "true" + access.token.claim: "true" + introspection.token.claim: "true" + userinfo.token.claim: "true" + claim.name: groups + jsonType.label: String + +clients: + - clientId: "$(OIDC_CLIENT_ID)" + name: "$(OIDC_CLIENT_ID)" + enabled: true + rootUrl: "http://localhost:8080" + adminUrl: "http://localhost:8080" + redirectUris: + - "http://localhost:8080/*" + webOrigins: + - "http://localhost:8080" + publicClient: false + secret: "$(OIDC_CLIENT_SECRET)" + standardFlowEnabled: true + directAccessGrantsEnabled: true + serviceAccountsEnabled: true + frontchannelLogout: true + attributes: + backchannel.logout.session.required: "true" + frontchannel.logout.session.required: "true" + defaultClientScopes: + - profile + - email + - berkeley_edu_groups + +groups: + - name: "$(OIDC_ADMIN_GROUP)" + - name: "$(OIDC_USER_GROUP)" + - name: cn=edu:berkeley:org:libr:unrelated:group,ou=campus groups,dc=berkeley,dc=edu + +users: + - username: testadmin + enabled: true + email: testadmin@example.edu + firstName: Test + lastName: Admin + credentials: + - type: password + value: testadmin + temporary: false + groups: + - "$(OIDC_ADMIN_GROUP)" + - "$(OIDC_USER_GROUP)" + + - username: testuser + enabled: true + email: testuser@example.edu + firstName: Test + lastName: User + credentials: + - type: password + value: testuser + temporary: false + groups: + - "$(OIDC_USER_GROUP)" + + - username: testpublic + enabled: true + email: testpublic@example.edu + firstName: Test + lastName: Public + credentials: + - type: password + value: testpublic + temporary: false + groups: + - cn=edu:berkeley:org:libr:unrelated:group,ou=campus groups,dc=berkeley,dc=edu diff --git a/docker-compose.yml b/docker-compose.yml index e69bfea..ee18185 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,6 +54,7 @@ services: geoserver: image: docker.osgeo.org/geoserver:2.28-latest + # entrypoint: ["bash", "/opt/geoserver-with-oidc-seed.sh"] ports: - 8080:8080 environment: &geoserver-environment @@ -61,6 +62,7 @@ services: SKIP_DEMO_DATA: "true" INSTALL_EXTENSIONS: "true" COMMUNITY_EXTENSIONS: "sec-oidc" + PROXY_BASE_URL: depends_on: keycloak-config: condition: service_completed_successfully @@ -73,7 +75,11 @@ services: extra_hosts: - keycloak.localhost:host-gateway volumes: - - ./data/geoserver/geoserver_data:/opt/geoserver_data + # - ./bin/geoserver-with-oidc-seed.sh:/opt/geoserver-with-oidc-seed.sh:ro + # - ./data/geoserver/geoserver_data:/opt/geoserver_data + - ./data/geoserver/geoserver_oidc_seed:/opt/seed + # - ./data/geoserver/geoserver_oidc_seed/security/config.xml:/opt/geoserver_data/security/config.xml:ro + # - ./data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml:/opt/geoserver_data/security/filter/keycloak/config.xml:ro - ./data/geoserver/public:/srv/geofiles geoserver-secure: @@ -93,7 +99,7 @@ services: extra_hosts: - keycloak.localhost:host-gateway volumes: - - ./data/geoserver/geoserver_secure_data:/opt/geoserver_data + # - ./data/geoserver/geoserver_secure_data:/opt/geoserver_data - ./data/geoserver/UCB:/srv/geofiles postgres: diff --git a/env.example b/env.example new file mode 100644 index 0000000..5434ea3 --- /dev/null +++ b/env.example @@ -0,0 +1,6 @@ +OIDC_CLIENT_ID=geoserver-local +OIDC_CLIENT_SECRET=change-me +OIDC_ADMIN_GROUP="cn=edu:berkeley:org:libr:geoserver:admins,ou=campus groups,dc=berkeley,dc=edu" +OIDC_NAME="keycloak" +OIDC_USER_GROUP="cn=edu:berkeley:org:libr:geoserver:users,ou=campus groups,dc=berkeley,dc=edu" +OIDC_WELL_KNOWN="http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration" \ No newline at end of file From 96fde384bd687d675869717dc92d9526ca5d98af Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Mon, 6 Jul 2026 12:01:42 -0400 Subject: [PATCH 3/9] cleanup --- docker-compose.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ee18185..55b232c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -75,11 +75,7 @@ services: extra_hosts: - keycloak.localhost:host-gateway volumes: - # - ./bin/geoserver-with-oidc-seed.sh:/opt/geoserver-with-oidc-seed.sh:ro - # - ./data/geoserver/geoserver_data:/opt/geoserver_data - ./data/geoserver/geoserver_oidc_seed:/opt/seed - # - ./data/geoserver/geoserver_oidc_seed/security/config.xml:/opt/geoserver_data/security/config.xml:ro - # - ./data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml:/opt/geoserver_data/security/filter/keycloak/config.xml:ro - ./data/geoserver/public:/srv/geofiles geoserver-secure: @@ -99,7 +95,6 @@ services: extra_hosts: - keycloak.localhost:host-gateway volumes: - # - ./data/geoserver/geoserver_secure_data:/opt/geoserver_data - ./data/geoserver/UCB:/srv/geofiles postgres: From d45c9ef679f92a32006067aa93aec2d3e278c1b0 Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Tue, 7 Jul 2026 14:52:56 -0400 Subject: [PATCH 4/9] refactor with geoserver-config service --- bin/geoserver-oidc-config.sh | 78 +++++++++++++++++++ .../geoserver_oidc_seed/security/config.xml | 2 +- .../security/filter/keycloak/config.xml | 2 +- .../keycloak/config/berkeley-local.yaml | 2 +- docker-compose.yml | 17 +++- 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 bin/geoserver-oidc-config.sh diff --git a/bin/geoserver-oidc-config.sh b/bin/geoserver-oidc-config.sh new file mode 100644 index 0000000..0cefbd1 --- /dev/null +++ b/bin/geoserver-oidc-config.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env sh +set -eu + +GEOSERVER_REST="${GEOSERVER_REST:-http://geoserver:8080/geoserver/rest}" +GEOSERVER_AUTH="${GEOSERVER_AUTH:-admin:geoserver}" + +echo "Creating keycloak OIDC auth filter..." +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + --request POST \ + --url "${GEOSERVER_REST}/security/authfilters" \ + --user "${GEOSERVER_AUTH}" \ + --header 'Content-Type: application/xml; charset=utf-8' \ + --data " + keycloak + org.geoserver.security.oauth2.login.GeoServerOAuth2LoginAuthenticationFilter + IdToken + http://localhost:8080/geoserver/ + false + email + http://localhost:8080/geoserver/web/login/oauth2/code/google + false + id + http://localhost:8080/geoserver/web/login/oauth2/code/gitHub + false + sub + http://localhost:8080/geoserver/web/login/oauth2/code/microsoft + openid profile email + true + ${OIDC_CLIENT_ID} + ${OIDC_CLIENT_SECRET} + email + http://localhost:8080/geoserver/web/login/oauth2/code/oidc + openid berkeley_edu_groups email profile + http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/token + http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/auth + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/userinfo + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/certs + http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/logout + false + false + true + false + false + false + preferred_username + http://localhost:8080/geoserver/web/ + true + false + false + testadmin=ADMIN + false +") + +case "$HTTP_STATUS" in + 200|201) echo "Auth filter created." ;; + *) echo "ERROR: Unexpected status creating auth filter: $HTTP_STATUS"; exit 1 ;; +esac + +echo "Updating web filter chain..." +FILTER_CHAIN_UPDATE_HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + --request PUT \ + --url "${GEOSERVER_REST}/security/filterchain/web" \ + --user "${GEOSERVER_AUTH}" \ + --header 'Content-Type: application/xml; charset=utf-8' \ + --data ' + rememberme + form + keycloak + anonymous + ') + +case "$FILTER_CHAIN_UPDATE_HTTP_STATUS" in + 200) echo "Web filter chain updated." ;; + *) echo "ERROR: Unexpected status updating web filter chain: $FILTER_CHAIN_UPDATE_HTTP_STATUS"; exit 1 ;; +esac + +echo "Done." \ No newline at end of file diff --git a/data/geoserver/geoserver_oidc_seed/security/config.xml b/data/geoserver/geoserver_oidc_seed/security/config.xml index 9658cf1..b88b784 100644 --- a/data/geoserver/geoserver_oidc_seed/security/config.xml +++ b/data/geoserver/geoserver_oidc_seed/security/config.xml @@ -43,4 +43,4 @@ 127.0.0.1 - \ No newline at end of file + diff --git a/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml b/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml index 07b9df6..23f7913 100644 --- a/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml +++ b/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml @@ -39,4 +39,4 @@ false testadmin=ADMIN false - \ No newline at end of file + diff --git a/data/geoserver/keycloak/config/berkeley-local.yaml b/data/geoserver/keycloak/config/berkeley-local.yaml index 2269a3a..ad00441 100644 --- a/data/geoserver/keycloak/config/berkeley-local.yaml +++ b/data/geoserver/keycloak/config/berkeley-local.yaml @@ -61,7 +61,7 @@ clients: groups: - name: "$(OIDC_ADMIN_GROUP)" - name: "$(OIDC_USER_GROUP)" - - name: cn=edu:berkeley:org:libr:unrelated:group,ou=campus groups,dc=berkeley,dc=edu + - name: "cn=edu:berkeley:org:libr:unrelated:group,ou=campus groups,dc=berkeley,dc=edu" users: - username: testadmin diff --git a/docker-compose.yml b/docker-compose.yml index 55b232c..31661a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,7 +54,6 @@ services: geoserver: image: docker.osgeo.org/geoserver:2.28-latest - # entrypoint: ["bash", "/opt/geoserver-with-oidc-seed.sh"] ports: - 8080:8080 environment: &geoserver-environment @@ -75,7 +74,7 @@ services: extra_hosts: - keycloak.localhost:host-gateway volumes: - - ./data/geoserver/geoserver_oidc_seed:/opt/seed + # - ./data/geoserver/geoserver_data:/opt/geoserver_data - ./data/geoserver/public:/srv/geofiles geoserver-secure: @@ -97,6 +96,20 @@ services: volumes: - ./data/geoserver/UCB:/srv/geofiles + geoserver-config: + image: curlimages/curl:latest + depends_on: + geoserver: + condition: service_healthy + entrypoint: ["/bin/sh"] + command: ["/scripts/geoserver-oidc-config.sh"] + environment: + OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} + OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} + restart: on-failure:3 + volumes: + - ./bin/geoserver-oidc-config.sh:/scripts/geoserver-oidc-config.sh:ro + postgres: image: postgres:16-alpine environment: From e4403d33c6d330237834d2d5a4a227e134a80206 Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Tue, 7 Jul 2026 14:54:56 -0400 Subject: [PATCH 5/9] update readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 10dda75..1c43bee 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,9 @@ cp .env.example .env # Build the image docker compose build -# First time you run locally, you need to seed the OIDC config files and restart geoserver. -docker compose up -d && ./bin/add-keycloak-to-geoserver.sh +# The geoserver-config will need to run every time unless you mount the geoserver_data volume +# and comment out the geoserver-config service. +docker compose up -d # Use `docker compose watch` to automatically rebuild on changes. This takes over the terminal # you run it in, so you'll need to either background it (`screen` works great) or open a different window. From d28b715108f8ed59d6d5f9ff941029739b586bfb Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Tue, 7 Jul 2026 15:01:15 -0400 Subject: [PATCH 6/9] ci resets --- docker-compose.ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 928ef09..ccf0d06 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -15,5 +15,13 @@ services: geoserver-secure: !reset + geoserver-config: !reset + + postgres: !reset + + keycloak: !reset + + keycloak-config: !reset + volumes: artifacts: From ea3b693ae80b45e884774cd432bc9fc5627cda4f Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Tue, 7 Jul 2026 15:06:29 -0400 Subject: [PATCH 7/9] cleanup --- .gitignore | 5 -- .../geoserver_oidc_seed/security/config.xml | 46 ------------------- .../security/filter/keycloak/config.xml | 42 ----------------- 3 files changed, 93 deletions(-) delete mode 100644 data/geoserver/geoserver_oidc_seed/security/config.xml delete mode 100644 data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml diff --git a/.gitignore b/.gitignore index c75fea4..645de42 100644 --- a/.gitignore +++ b/.gitignore @@ -17,13 +17,8 @@ /data/gingr/processing/* !/data/geoserver/ /data/geoserver/* -!/data/geoserver/geoserver_oidc_seed/ !/data/geoserver/keycloak/ -# !data/geoserver/geoserver_oidc_seed/security/config.xml -# !data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml -# !data/geoserver/keycloak/config/berkeley-local.yaml - /solr/geodata-test/data /solr/geodata-test/data /solr/geodata/data diff --git a/data/geoserver/geoserver_oidc_seed/security/config.xml b/data/geoserver/geoserver_oidc_seed/security/config.xml deleted file mode 100644 index b88b784..0000000 --- a/data/geoserver/geoserver_oidc_seed/security/config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - default - - default - - pbePasswordEncoder - false - - - rememberme - form - keycloak - anonymous - - - form - - - formLogout - - - basic - anonymous - - - basic - - - basic - anonymous - - - - org.geoserver.security.rememberme.GeoServerTokenBasedRememberMeServices - geoserver - - - true - 1 - 5 - 100 - - 127.0.0.1 - - - diff --git a/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml b/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml deleted file mode 100644 index 23f7913..0000000 --- a/data/geoserver/geoserver_oidc_seed/security/filter/keycloak/config.xml +++ /dev/null @@ -1,42 +0,0 @@ - - -5c6b9fa7:19f233f931b:-7fe3 - keycloak - org.geoserver.security.oauth2.login.GeoServerOAuth2LoginAuthenticationFilter - IdToken - http://localhost:8080/geoserver/ - false - email - http://localhost:8080/geoserver/web/login/oauth2/code/google - false - id - http://localhost:8080/geoserver/web/login/oauth2/code/gitHub - false - sub - http://localhost:8080/geoserver/web/login/oauth2/code/microsoft - openid profile email - true - geoserver-local - change-me - email - http://localhost:8080/geoserver/web/login/oauth2/code/oidc - openid berkeley_edu_groups email profile - http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration - http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/token - http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/auth - http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/userinfo - http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/certs - http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/logout - false - false - true - false - false - false - preferred_username - http://localhost:8080/geoserver/web/ - true - false - false - testadmin=ADMIN - false - From 97c053f474ad03e15be16cc843bf167ffa188f2f Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Wed, 8 Jul 2026 11:32:31 -0400 Subject: [PATCH 8/9] fixes from PR comments --- bin/add-keycloak-to-geoserver.sh | 29 ----------------------------- bin/geoserver-oidc-config.sh | 2 +- docker-compose.yml | 2 +- env.example | 2 +- 4 files changed, 3 insertions(+), 32 deletions(-) delete mode 100755 bin/add-keycloak-to-geoserver.sh diff --git a/bin/add-keycloak-to-geoserver.sh b/bin/add-keycloak-to-geoserver.sh deleted file mode 100755 index 95dc8a3..0000000 --- a/bin/add-keycloak-to-geoserver.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HEALTH_URL="${GEOSERVER_URL:-http://localhost:8080/geoserver/web/}" -WAIT_ATTEMPTS="${WAIT_ATTEMPTS:-60}" - -echo "Waiting for geoserver to be healthy..." -for ((attempt = 1; attempt <= WAIT_ATTEMPTS; attempt++)); do - if curl -fsS "$HEALTH_URL" >/dev/null 2>&1; then - echo "GeoServer is healthy (attempt $attempt)" - break - fi - if [[ $attempt -eq $WAIT_ATTEMPTS ]]; then - echo "ERROR: GeoServer did not become healthy after $WAIT_ATTEMPTS attempts" >&2 - exit 1 - fi - echo " attempt $attempt/$WAIT_ATTEMPTS — not ready yet, waiting 10s..." - sleep 10 -done - -echo "Copying OIDC seed config files..." -docker compose exec geoserver mkdir -p /opt/geoserver_data/security/filter/keycloak -docker compose exec geoserver cp /opt/seed/security/config.xml /opt/geoserver_data/security/config.xml -docker compose exec geoserver cp /opt/seed/security/filter/keycloak/config.xml /opt/geoserver_data/security/filter/keycloak/config.xml - -echo "Restarting geoserver..." -docker compose restart geoserver - -echo "Done." \ No newline at end of file diff --git a/bin/geoserver-oidc-config.sh b/bin/geoserver-oidc-config.sh index 0cefbd1..fc15ed8 100644 --- a/bin/geoserver-oidc-config.sh +++ b/bin/geoserver-oidc-config.sh @@ -75,4 +75,4 @@ case "$FILTER_CHAIN_UPDATE_HTTP_STATUS" in *) echo "ERROR: Unexpected status updating web filter chain: $FILTER_CHAIN_UPDATE_HTTP_STATUS"; exit 1 ;; esac -echo "Done." \ No newline at end of file +echo "Done." diff --git a/docker-compose.yml b/docker-compose.yml index 31661a9..fce4e52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -111,7 +111,7 @@ services: - ./bin/geoserver-oidc-config.sh:/scripts/geoserver-oidc-config.sh:ro postgres: - image: postgres:16-alpine + image: postgres:18-alpine environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak diff --git a/env.example b/env.example index 5434ea3..9a20406 100644 --- a/env.example +++ b/env.example @@ -3,4 +3,4 @@ OIDC_CLIENT_SECRET=change-me OIDC_ADMIN_GROUP="cn=edu:berkeley:org:libr:geoserver:admins,ou=campus groups,dc=berkeley,dc=edu" OIDC_NAME="keycloak" OIDC_USER_GROUP="cn=edu:berkeley:org:libr:geoserver:users,ou=campus groups,dc=berkeley,dc=edu" -OIDC_WELL_KNOWN="http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration" \ No newline at end of file +OIDC_WELL_KNOWN="http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration" From 4f0ffbd55d0963c075d363d0a83c998a9d765bb6 Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Wed, 8 Jul 2026 15:18:33 -0400 Subject: [PATCH 9/9] add keycloak to geoserver-secure - also volume mount fix for postgres 18 image --- bin/geoserver-oidc-config.sh | 143 ++++++++++++++++++----------------- docker-compose.yml | 5 +- 2 files changed, 78 insertions(+), 70 deletions(-) diff --git a/bin/geoserver-oidc-config.sh b/bin/geoserver-oidc-config.sh index fc15ed8..7e6dfcd 100644 --- a/bin/geoserver-oidc-config.sh +++ b/bin/geoserver-oidc-config.sh @@ -1,78 +1,83 @@ #!/usr/bin/env sh set -eu -GEOSERVER_REST="${GEOSERVER_REST:-http://geoserver:8080/geoserver/rest}" GEOSERVER_AUTH="${GEOSERVER_AUTH:-admin:geoserver}" -echo "Creating keycloak OIDC auth filter..." -HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ - --request POST \ - --url "${GEOSERVER_REST}/security/authfilters" \ - --user "${GEOSERVER_AUTH}" \ - --header 'Content-Type: application/xml; charset=utf-8' \ - --data " - keycloak - org.geoserver.security.oauth2.login.GeoServerOAuth2LoginAuthenticationFilter - IdToken - http://localhost:8080/geoserver/ - false - email - http://localhost:8080/geoserver/web/login/oauth2/code/google - false - id - http://localhost:8080/geoserver/web/login/oauth2/code/gitHub - false - sub - http://localhost:8080/geoserver/web/login/oauth2/code/microsoft - openid profile email - true - ${OIDC_CLIENT_ID} - ${OIDC_CLIENT_SECRET} - email - http://localhost:8080/geoserver/web/login/oauth2/code/oidc - openid berkeley_edu_groups email profile - http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration - http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/token - http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/auth - http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/userinfo - http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/certs - http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/logout - false - false - true - false - false - false - preferred_username - http://localhost:8080/geoserver/web/ - true - false - false - testadmin=ADMIN - false -") +# This script assumes both geoserver and geoserver-secure are healthy +# and configured exactly the same. +for GEOSERVER_HOST in geoserver geoserver-secure; do + GEOSERVER_REST="http://${GEOSERVER_HOST}:8080/geoserver/rest" -case "$HTTP_STATUS" in - 200|201) echo "Auth filter created." ;; - *) echo "ERROR: Unexpected status creating auth filter: $HTTP_STATUS"; exit 1 ;; -esac + echo "Creating keycloak OIDC auth filter on ${GEOSERVER_HOST}..." + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + --request POST \ + --url "${GEOSERVER_REST}/security/authfilters" \ + --user "${GEOSERVER_AUTH}" \ + --header 'Content-Type: application/xml; charset=utf-8' \ + --data " + keycloak + org.geoserver.security.oauth2.login.GeoServerOAuth2LoginAuthenticationFilter + IdToken + http://localhost:8080/geoserver/ + false + email + http://localhost:8080/geoserver/web/login/oauth2/code/google + false + id + http://localhost:8080/geoserver/web/login/oauth2/code/gitHub + false + sub + http://localhost:8080/geoserver/web/login/oauth2/code/microsoft + openid profile email + true + ${OIDC_CLIENT_ID} + ${OIDC_CLIENT_SECRET} + email + http://localhost:8080/geoserver/web/login/oauth2/code/oidc + openid berkeley_edu_groups email profile + http://keycloak:8180/realms/berkeley-local/.well-known/openid-configuration + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/token + http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/auth + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/userinfo + http://keycloak:8180/realms/berkeley-local/protocol/openid-connect/certs + http://keycloak.localhost:8180/realms/berkeley-local/protocol/openid-connect/logout + false + false + true + false + false + false + preferred_username + http://localhost:8080/geoserver/web/ + true + false + false + testadmin=ADMIN + false + ") -echo "Updating web filter chain..." -FILTER_CHAIN_UPDATE_HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ - --request PUT \ - --url "${GEOSERVER_REST}/security/filterchain/web" \ - --user "${GEOSERVER_AUTH}" \ - --header 'Content-Type: application/xml; charset=utf-8' \ - --data ' - rememberme - form - keycloak - anonymous - ') + case "$HTTP_STATUS" in + 200|201) echo "Auth filter created." ;; + *) echo "ERROR: Unexpected status creating auth filter on ${GEOSERVER_HOST}: $HTTP_STATUS"; exit 1 ;; + esac -case "$FILTER_CHAIN_UPDATE_HTTP_STATUS" in - 200) echo "Web filter chain updated." ;; - *) echo "ERROR: Unexpected status updating web filter chain: $FILTER_CHAIN_UPDATE_HTTP_STATUS"; exit 1 ;; -esac + echo "Updating web filter chain on ${GEOSERVER_HOST}..." + FILTER_CHAIN_UPDATE_HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + --request PUT \ + --url "${GEOSERVER_REST}/security/filterchain/web" \ + --user "${GEOSERVER_AUTH}" \ + --header 'Content-Type: application/xml; charset=utf-8' \ + --data ' + rememberme + form + keycloak + anonymous + ') -echo "Done." + case "$FILTER_CHAIN_UPDATE_HTTP_STATUS" in + 200) echo "Web filter chain updated." ;; + *) echo "ERROR: Unexpected status updating web filter chain: $FILTER_CHAIN_UPDATE_HTTP_STATUS"; exit 1 ;; + esac + + echo "Done with ${GEOSERVER_HOST}." +done diff --git a/docker-compose.yml b/docker-compose.yml index fce4e52..772b58f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,6 +74,7 @@ services: extra_hosts: - keycloak.localhost:host-gateway volumes: + # Uncomment and comment out the geoserver-config service to persist geoserver between runs. # - ./data/geoserver/geoserver_data:/opt/geoserver_data - ./data/geoserver/public:/srv/geofiles @@ -101,6 +102,8 @@ services: depends_on: geoserver: condition: service_healthy + geoserver-secure: + condition: service_healthy entrypoint: ["/bin/sh"] command: ["/scripts/geoserver-oidc-config.sh"] environment: @@ -122,7 +125,7 @@ services: timeout: 5s retries: 5 volumes: - - postgres_data:/var/lib/postgresql/data + - postgres_data:/var/lib/postgresql keycloak: command: start-dev