diff --git a/Dockerfile b/Dockerfile index fe5d377..146af35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,57 +1,5 @@ -FROM tomcat:9-jre17-temurin-focal AS base - ARG GEOSERVER_VERSION="2.28.4" -ENV GEOSERVER_VERSION=$GEOSERVER_VERSION -ENV GEOSERVER_URL="https://sourceforge.net/projects/geoserver/files/GeoServer/${GEOSERVER_VERSION}/geoserver-${GEOSERVER_VERSION}-war.zip" -ENV GEOSERVER_LIB_DIR="/usr/local/tomcat/webapps/geoserver/WEB-INF/lib" -ENV GDAL_PLUGIN_URL="https://sourceforge.net/projects/geoserver/files/GeoServer/${GEOSERVER_VERSION}/extensions/geoserver-${GEOSERVER_VERSION}-gdal-plugin.zip" -ENV CAS_PLUGIN_URL="https://sourceforge.net/projects/geoserver/files/GeoServer/${GEOSERVER_VERSION}/extensions/geoserver-${GEOSERVER_VERSION}-cas-plugin.zip" - -RUN apt update && \ - apt -y --no-install-recommends install \ - gdal-bin \ - libgdal-dev \ - libgdal-java \ - libsqlite3-dev \ - sqlite3 \ - swig \ - unzip \ - wget && \ - apt clean && \ - rm -rf /var/lib/apt/lists/* - -FROM base AS download-geoserver -RUN wget -qO /tmp/geoserver-war.zip "${GEOSERVER_URL}" && \ - unzip /tmp/geoserver-war.zip -d /tmp/geoserver-war && \ - unzip /tmp/geoserver-war/geoserver.war -d /usr/local/tomcat/webapps/geoserver && \ - rm -rf /tmp/geoserver* - -FROM base AS download-gdal-plugin -RUN wget -qO /tmp/gdal-plugin.zip $GDAL_PLUGIN_URL && \ - unzip -o -d /tmp/gdal-plugin /tmp/gdal-plugin.zip && \ - rm -f /tmp/gdal-plugin.zip - -FROM base AS download-cas-plugin -RUN wget -qO /tmp/cas-plugin.zip $CAS_PLUGIN_URL && \ - unzip -o -d /tmp/cas-plugin /tmp/cas-plugin.zip && \ - rm -f /tmp/cas-plugin.zip - -FROM base AS final -COPY --from=download-geoserver /usr/local/tomcat/webapps/geoserver /usr/local/tomcat/webapps/geoserver -COPY --from=download-cas-plugin /tmp/cas-plugin/*.jar $GEOSERVER_LIB_DIR/ -COPY --from=download-gdal-plugin /tmp/gdal-plugin/*.jar $GEOSERVER_LIB_DIR/ - -# Downgrade the gdal jar so it matches the gdal version installed on the system -RUN rm -f $GEOSERVER_LIB_DIR/gdal-3.2.0.jar && \ - cp /usr/share/java/gdal.jar $GEOSERVER_LIB_DIR/gdal-3.0.4.jar -# Ensure libgdalalljni.so is in the load path. If this isn't found, GeoServer will log and warn -# about that on startup. -ENV LD_LIBRARY_PATH="/usr/lib/jni:$LD_LIBRARY_PATH" -ENV GDAL_DATA="/usr/share/gdal" +FROM docker.osgeo.org/geoserver:${GEOSERVER_VERSION}-gdal -# Redirect the root path to /geoserver/web -# Inspired by https://github.com/geoserver/docker/blob/master/startup.sh -RUN mkdir -p $CATALINA_HOME/webapps/ROOT -COPY files/index.jsp $CATALINA_HOME/webapps/ROOT/ COPY bin/run-tests /usr/local/bin/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 04b1ea3..7a67dea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,8 +2,91 @@ services: app: build: args: - - GEOSERVER_VERSION=${GEOSERVER_VERSION:-2.28.0} + - GEOSERVER_VERSION=${GEOSERVER_VERSION:-2.28.4} context: . image: ucblib/geoserver:${DOCKER_TAG:-development} + environment: + INSTALL_EXTENSIONS: "true" + STABLE_EXTENSIONS: gdal + COMMUNITY_EXTENSIONS: sec-keycloak,sec-oidc + ROOT_WEBAPP_REDIRECT: "true" + extra_hosts: + - keycloak.localhost:host-gateway ports: - 8080:8080 + volumes: + - ./geoserver_data:/opt/geoserver_data + + 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: + - ./keycloak/config:/config + +volumes: + geoserver_data: + postgres_data: diff --git a/env.sample b/env.sample new file mode 100644 index 0000000..5c2eaec --- /dev/null +++ b/env.sample @@ -0,0 +1,6 @@ +OIDC_ADMIN_GROUP="cn=edu:berkeley:org:libr:geoserver:admins,ou=campus groups,dc=berkeley,dc=edu" +OIDC_CLIENT_ID=geoserver +OIDC_CLIENT_SECRET=some-secret-hash +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.localhost:8180/realms/berkeley-local/.well-known/openid-configuration" diff --git a/keycloak/config/berkeley-local.yaml b/keycloak/config/berkeley-local.yaml new file mode 100644 index 0000000..2269a3a --- /dev/null +++ b/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