diff --git a/docker/geoserver/Dockerfile b/docker/geoserver/Dockerfile index cb22c8e..6cd9a1f 100644 --- a/docker/geoserver/Dockerfile +++ b/docker/geoserver/Dockerfile @@ -29,29 +29,51 @@ RUN cd /usr/local/tomcat/webapps \ COPY applicationContext.xml /usr/local/tomcat/webapps/geoserver/WEB-INF/classes/applicationContext.xml +# exclude wrong dependencies +RUN sed -i -e 's/xom-\*\.jar/xom-\*\.jar,bcprov\*\.jar/g' /usr/local/tomcat/conf/catalina.properties + +# proactively create localhost directory, so Tomcat will not create misleading error upon startup in a readonly setup +RUN mkdir -p /usr/local/tomcat/conf/Catalina/localhost + VOLUME $GEOSERVER_DATA_DIR # copy the script and perform the run of scripts from entrypoint.sh -RUN mkdir -p /usr/local/tomcat/tmp -WORKDIR /usr/local/tomcat/tmp +RUN mkdir -p /opt/geoserver/bin +WORKDIR /opt/geoserver/bin COPY set_geoserver_auth.sh \ entrypoint.sh \ tasks.py \ multidump.sh \ multidump-alt.sh \ - /usr/local/tomcat/tmp/ + /opt/geoserver/bin/ COPY ./templates /templates -RUN chmod +x \ - /usr/local/tomcat/tmp/set_geoserver_auth.sh \ - /usr/local/tomcat/tmp/entrypoint.sh +RUN chmod +x /opt/geoserver/bin/set_geoserver_auth.sh \ + /opt/geoserver/bin/entrypoint.sh + +# for backwards compatibility of Docker image +RUN mkdir -p /usr/local/tomcat/tmp \ + && ln -s /opt/geoserver/bin/entrypoint.sh /usr/local/tomcat/tmp/entrypoint.sh RUN apt-get install -y procps less && \ apt-get install -y python3 python3-pip python3-dev RUN pip install j2cli invoke==2.2.0 requests==2.31.0 -ENV JAVA_OPTS="-Djava.awt.headless=true -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:MaxPermSize=512m -XX:PermSize=256m -Xms512m -Xmx2048m -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://geoserver:8080/geoserver/pdf -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine" +ENV JAVA_OPTS="-Djava.awt.headless=true -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/tmp/jvm.log -XX:MaxPermSize=512m -XX:PermSize=256m -Xms512m -Xmx2048m -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://geoserver:8080/geoserver/pdf -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine" + +# --- Non-root / arbitrary-UID support (opt-in; image still runs as root by default) +ARG GEOSERVER_UID=1000 +ARG GEOSERVER_HOME=/home/geoserver + +RUN set -eux; \ + useradd -u ${GEOSERVER_UID} -g 0 -d ${GEOSERVER_HOME} -m geoserver; \ + if [ -f /etc/skel/.bashrc ]; then cp /etc/skel/.bashrc ${GEOSERVER_HOME}/.bashrc; else touch ${GEOSERVER_HOME}/.bashrc; fi; \ + chown -R ${GEOSERVER_UID}:0 /usr/local/tomcat /geoserver_data ${GEOSERVER_HOME}; \ + chmod -R g=u /usr/local/tomcat /geoserver_data ${GEOSERVER_HOME} + +# NOTE: no USER statement — the image runs as root by default (backward compatible). +# To run non-root, set runAsUser / --user to any UID with group 0 (e.g. 1000:0). -CMD ["/usr/local/tomcat/tmp/entrypoint.sh"] +CMD ["/opt/geoserver/bin/entrypoint.sh"] diff --git a/docker/geoserver/entrypoint.sh b/docker/geoserver/entrypoint.sh index e7d395f..7cf3088 100644 --- a/docker/geoserver/entrypoint.sh +++ b/docker/geoserver/entrypoint.sh @@ -1,7 +1,16 @@ #!/bin/bash set -e -source /root/.bashrc +# Writable home for arbitrary (non-root) UIDs +# Defaults to /root for backward compatibility when running as root. +export GEOSERVER_HOME="${GEOSERVER_HOME:-/root}" +if [ ! -w "${GEOSERVER_HOME}" ]; then + echo "ERROR: GEOSERVER_HOME='${GEOSERVER_HOME}' is not writable by the current user (UID $(id -u))." >&2 + echo " When running as a non-root user, set GEOSERVER_HOME to a writable path (e.g. /tmp)." >&2 + exit 1 +fi + +[ -f "${GEOSERVER_HOME}/.bashrc" ] && source "${GEOSERVER_HOME}/.bashrc" INVOKE_LOG_STDOUT=${INVOKE_LOG_STDOUT:-TRUE} @@ -19,40 +28,40 @@ invoke () { if [ -n "$GEONODE_LB_HOST_IP" ]; then echo "GEONODE_LB_HOST_IP is defined and not empty with the value '$GEONODE_LB_HOST_IP' " - echo export GEONODE_LB_HOST_IP=${GEONODE_LB_HOST_IP} >> /root/.override_env + echo export GEONODE_LB_HOST_IP=${GEONODE_LB_HOST_IP} >> "${GEOSERVER_HOME}/.override_env" else echo "GEONODE_LB_HOST_IP is either not defined or empty setting the value to 'django' " - echo export GEONODE_LB_HOST_IP=django >> /root/.override_env + echo export GEONODE_LB_HOST_IP=django >> "${GEOSERVER_HOME}/.override_env" export GEONODE_LB_HOST_IP=django fi if [ -n "$GEONODE_LB_PORT" ]; then echo "GEONODE_LB_HOST_IP is defined and not empty with the value '$GEONODE_LB_PORT' " - echo export GEONODE_LB_PORT=${GEONODE_LB_PORT} >> /root/.override_env + echo export GEONODE_LB_PORT=${GEONODE_LB_PORT} >> "${GEOSERVER_HOME}/.override_env" else echo "GEONODE_LB_PORT is either not defined or empty setting the value to '8000' " - echo export GEONODE_LB_PORT=8000 >> /root/.override_env + echo export GEONODE_LB_PORT=8000 >> "${GEOSERVER_HOME}/.override_env" export GEONODE_LB_PORT=8000 fi if [ -n "$GEOSERVER_LB_HOST_IP" ]; then echo "GEOSERVER_LB_HOST_IP is defined and not empty with the value '$GEOSERVER_LB_HOST_IP' " - echo export GEOSERVER_LB_HOST_IP=${GEOSERVER_LB_HOST_IP} >> /root/.override_env + echo export GEOSERVER_LB_HOST_IP=${GEOSERVER_LB_HOST_IP} >> "${GEOSERVER_HOME}/.override_env" else echo "GEOSERVER_LB_HOST_IP is either not defined or empty setting the value to 'geoserver' " - echo export GEOSERVER_LB_HOST_IP=geoserver >> /root/.override_env + echo export GEOSERVER_LB_HOST_IP=geoserver >> "${GEOSERVER_HOME}/.override_env" export GEOSERVER_LB_HOST_IP=geoserver fi if [ -n "$GEOSERVER_LB_PORT" ]; then echo "GEOSERVER_LB_PORT is defined and not empty with the value '$GEOSERVER_LB_PORT' " - echo export GEOSERVER_LB_PORT=${GEOSERVER_LB_PORT} >> /root/.override_env + echo export GEOSERVER_LB_PORT=${GEOSERVER_LB_PORT} >> "${GEOSERVER_HOME}/.override_env" else echo "GEOSERVER_LB_PORT is either not defined or empty setting the value to '8000' " - echo export GEOSERVER_LB_PORT=8080 >> /root/.override_env + echo export GEOSERVER_LB_PORT=8080 >> "${GEOSERVER_HOME}/.override_env" export GEOSERVER_LB_PORT=8080 fi @@ -60,10 +69,10 @@ fi if [ -n "$DATABASE_HOST" ]; then echo "DATABASE_HOST is defined and not empty with the value '$DATABASE_HOST' " - echo export DATABASE_HOST=${DATABASE_HOST} >> /root/.override_env + echo export DATABASE_HOST=${DATABASE_HOST} >> "${GEOSERVER_HOME}/.override_env" else echo "DATABASE_HOST is either not defined or empty setting the value to 'db' " - echo export DATABASE_HOST=db >> /root/.override_env + echo export DATABASE_HOST=db >> "${GEOSERVER_HOME}/.override_env" export DATABASE_HOST=db fi @@ -71,10 +80,10 @@ fi if [ -n "$DATABASE_PORT" ]; then echo "DATABASE_PORT is defined and not empty with the value '$DATABASE_PORT' " - echo export DATABASE_HOST=${DATABASE_PORT} >> /root/.override_env + echo export DATABASE_HOST=${DATABASE_PORT} >> "${GEOSERVER_HOME}/.override_env" else echo "DATABASE_PORT is either not defined or empty setting the value to '5432' " - echo export DATABASE_PORT=5432 >> /root/.override_env + echo export DATABASE_PORT=5432 >> "${GEOSERVER_HOME}/.override_env" export DATABASE_PORT=5432 fi @@ -82,10 +91,10 @@ fi if [ -n "$GEONODE_GEODATABASE" ]; then echo "GEONODE_GEODATABASE is defined and not empty with the value '$GEONODE_GEODATABASE' " - echo export GEONODE_GEODATABASE=${GEONODE_GEODATABASE} >> /root/.override_env + echo export GEONODE_GEODATABASE=${GEONODE_GEODATABASE} >> "${GEOSERVER_HOME}/.override_env" else echo "GEONODE_GEODATABASE is either not defined or empty setting the value '${COMPOSE_PROJECT_NAME}_data' " - echo export GEONODE_GEODATABASE=${COMPOSE_PROJECT_NAME}_data >> /root/.override_env + echo export GEONODE_GEODATABASE=${COMPOSE_PROJECT_NAME}_data >> "${GEOSERVER_HOME}/.override_env" export GEONODE_GEODATABASE=${COMPOSE_PROJECT_NAME}_data fi @@ -93,10 +102,10 @@ fi if [ -n "$GEONODE_GEODATABASE_USER" ]; then echo "GEONODE_GEODATABASE_USER is defined and not empty with the value '$GEONODE_GEODATABASE_USER' " - echo export GEONODE_GEODATABASE_USER=${GEONODE_GEODATABASE_USER} >> /root/.override_env + echo export GEONODE_GEODATABASE_USER=${GEONODE_GEODATABASE_USER} >> "${GEOSERVER_HOME}/.override_env" else echo "GEONODE_GEODATABASE_USER is either not defined or empty setting the value '$GEONODE_GEODATABASE' " - echo export GEONODE_GEODATABASE_USER=${GEONODE_GEODATABASE} >> /root/.override_env + echo export GEONODE_GEODATABASE_USER=${GEONODE_GEODATABASE} >> "${GEOSERVER_HOME}/.override_env" export GEONODE_GEODATABASE_USER=${GEONODE_GEODATABASE} fi @@ -104,10 +113,10 @@ fi if [ -n "$GEONODE_GEODATABASE_PASSWORD" ]; then echo "GEONODE_GEODATABASE_PASSWORD is defined and not empty with the value '$GEONODE_GEODATABASE_PASSWORD' " - echo export GEONODE_GEODATABASE_PASSWORD=${GEONODE_GEODATABASE_PASSWORD} >> /root/.override_env + echo export GEONODE_GEODATABASE_PASSWORD=${GEONODE_GEODATABASE_PASSWORD} >> "${GEOSERVER_HOME}/.override_env" else echo "GEONODE_GEODATABASE_PASSWORD is either not defined or empty setting the value '${GEONODE_GEODATABASE}' " - echo export GEONODE_GEODATABASE_PASSWORD=${GEONODE_GEODATABASE} >> /root/.override_env + echo export GEONODE_GEODATABASE_PASSWORD=${GEONODE_GEODATABASE} >> "${GEOSERVER_HOME}/.override_env" export GEONODE_GEODATABASE_PASSWORD=${GEONODE_GEODATABASE} fi @@ -115,10 +124,10 @@ fi if [ -n "$GEONODE_GEODATABASE_SCHEMA" ]; then echo "GEONODE_GEODATABASE_SCHEMA is defined and not empty with the value '$GEONODE_GEODATABASE_SCHEMA' " - echo export GEONODE_GEODATABASE_SCHEMA=${GEONODE_GEODATABASE_SCHEMA} >> /root/.override_env + echo export GEONODE_GEODATABASE_SCHEMA=${GEONODE_GEODATABASE_SCHEMA} >> "${GEOSERVER_HOME}/.override_env" else echo "GEONODE_GEODATABASE_SCHEMA is either not defined or empty setting the value to 'public'" - echo export GEONODE_GEODATABASE_SCHEMA=public >> /root/.override_env + echo export GEONODE_GEODATABASE_SCHEMA=public >> "${GEOSERVER_HOME}/.override_env" export GEONODE_GEODATABASE_SCHEMA=public fi @@ -133,22 +142,22 @@ if [ -z `echo ${NGINX_BASE_URL} | sed 's/http:\/\/\([^:]*\).*/\1/'` ] then echo "NGINX_BASE_URL is empty so I'll use the default Geoserver base url" echo "Setting GEOSERVER_LOCATION='${SITEURL}'" - echo export GEOSERVER_LOCATION=${SITEURL} >> /root/.override_env + echo export GEOSERVER_LOCATION=${SITEURL} >> "${GEOSERVER_HOME}/.override_env" else echo "NGINX_BASE_URL is filled so GEOSERVER_LOCATION='${NGINX_BASE_URL}'" echo "Setting GEOSERVER_LOCATION='${NGINX_BASE_URL}'" - echo export GEOSERVER_LOCATION=${NGINX_BASE_URL} >> /root/.override_env + echo export GEOSERVER_LOCATION=${NGINX_BASE_URL} >> "${GEOSERVER_HOME}/.override_env" fi if [ -n "$SUBSTITUTION_URL" ]; then echo "SUBSTITUTION_URL is defined and not empty with the value '$SUBSTITUTION_URL'" echo "Setting GEONODE_LOCATION='${SUBSTITUTION_URL}' " - echo export GEONODE_LOCATION=${SUBSTITUTION_URL} >> /root/.override_env + echo export GEONODE_LOCATION=${SUBSTITUTION_URL} >> "${GEOSERVER_HOME}/.override_env" else echo "SUBSTITUTION_URL is either not defined or empty so I'll use the default GeoNode location " echo "Setting GEONODE_LOCATION='http://${GEONODE_LB_HOST_IP}:${GEONODE_LB_PORT}' " - echo export GEONODE_LOCATION=http://${GEONODE_LB_HOST_IP}:${GEONODE_LB_PORT} >> /root/.override_env + echo export GEONODE_LOCATION=http://${GEONODE_LB_HOST_IP}:${GEONODE_LB_PORT} >> "${GEOSERVER_HOME}/.override_env" fi # set basic tagname @@ -161,13 +170,13 @@ else # backup geonodeAuthProvider config.xml cp ${GEOSERVER_DATA_DIR}/security/auth/geonodeAuthProvider/config.xml ${GEOSERVER_DATA_DIR}/security/auth/geonodeAuthProvider/config.xml.orig # run the setting script for geonodeAuthProvider - /usr/local/tomcat/tmp/set_geoserver_auth.sh ${GEOSERVER_DATA_DIR}/security/auth/geonodeAuthProvider/config.xml ${GEOSERVER_DATA_DIR}/security/auth/geonodeAuthProvider/ ${TAGNAME[@]} > /dev/null 2>&1 + /opt/geoserver/bin/set_geoserver_auth.sh ${GEOSERVER_DATA_DIR}/security/auth/geonodeAuthProvider/config.xml ${GEOSERVER_DATA_DIR}/security/auth/geonodeAuthProvider/ ${TAGNAME[@]} > /dev/null 2>&1 fi # backup geonode REST role service config.xml cp "${GEOSERVER_DATA_DIR}/security/role/geonode REST role service/config.xml" "${GEOSERVER_DATA_DIR}/security/role/geonode REST role service/config.xml.orig" # run the setting script for geonode REST role service -/usr/local/tomcat/tmp/set_geoserver_auth.sh "${GEOSERVER_DATA_DIR}/security/role/geonode REST role service/config.xml" "${GEOSERVER_DATA_DIR}/security/role/geonode REST role service/" ${TAGNAME[@]} > /dev/null 2>&1 +/opt/geoserver/bin/set_geoserver_auth.sh "${GEOSERVER_DATA_DIR}/security/role/geonode REST role service/config.xml" "${GEOSERVER_DATA_DIR}/security/role/geonode REST role service/" ${TAGNAME[@]} > /dev/null 2>&1 # set oauth2 filter tagname TAGNAME=( "cliendId" "clientSecret" "accessTokenUri" "userAuthorizationUri" "redirectUri" "checkTokenEndpointUrl" "logoutUri" ) @@ -175,7 +184,7 @@ TAGNAME=( "cliendId" "clientSecret" "accessTokenUri" "userAuthorizationUri" "red # backup geonode-oauth2 config.xml cp ${GEOSERVER_DATA_DIR}/security/filter/geonode-oauth2/config.xml ${GEOSERVER_DATA_DIR}/security/filter/geonode-oauth2/config.xml.orig # run the setting script for geonode-oauth2 -/usr/local/tomcat/tmp/set_geoserver_auth.sh ${GEOSERVER_DATA_DIR}/security/filter/geonode-oauth2/config.xml ${GEOSERVER_DATA_DIR}/security/filter/geonode-oauth2/ "${TAGNAME[@]}" > /dev/null 2>&1 +/opt/geoserver/bin/set_geoserver_auth.sh ${GEOSERVER_DATA_DIR}/security/filter/geonode-oauth2/config.xml ${GEOSERVER_DATA_DIR}/security/filter/geonode-oauth2/ "${TAGNAME[@]}" > /dev/null 2>&1 # set global tagname TAGNAME=( "proxyBaseUrl" ) @@ -183,15 +192,12 @@ TAGNAME=( "proxyBaseUrl" ) # backup global.xml cp ${GEOSERVER_DATA_DIR}/global.xml ${GEOSERVER_DATA_DIR}/global.xml.orig # run the setting script for global configuration -/usr/local/tomcat/tmp/set_geoserver_auth.sh ${GEOSERVER_DATA_DIR}/global.xml ${GEOSERVER_DATA_DIR}/ ${TAGNAME[@]} > /dev/null 2>&1 +/opt/geoserver/bin/set_geoserver_auth.sh ${GEOSERVER_DATA_DIR}/global.xml ${GEOSERVER_DATA_DIR}/ ${TAGNAME[@]} > /dev/null 2>&1 # set correct amqp broker url sed -i -e 's/localhost/rabbitmq/g' ${GEOSERVER_DATA_DIR}/notifier/notifier.xml -# exclude wrong dependencies -sed -i -e 's/xom-\*\.jar/xom-\*\.jar,bcprov\*\.jar/g' /usr/local/tomcat/conf/catalina.properties - -# J2 templating for this docker image we should also do it for other configuration files in /usr/local/tomcat/tmp +# J2 templating for this docker image we should also do it for other configuration files in /opt/geoserver/bin declare -a geoserver_datadir_template_dirs=("geofence") @@ -240,7 +246,7 @@ if [ "${GEOSERVER_CORS_ENABLED}" = "true" ] || [ "${GEOSERVER_CORS_ENABLED}" = " fi fi -if [ ${FORCE_REINIT} = "true" ] || [ ${FORCE_REINIT} = "True" ] || [ ! -e "${GEOSERVER_DATA_DIR}/geoserver_init.lock" ]; then +if [ "${FORCE_REINIT:-false}" = "true" ] || [ "${FORCE_REINIT:-false}" = "True" ] || [ ! -e "${GEOSERVER_DATA_DIR}/geoserver_init.lock" ]; then # Run async configuration, it needs Geoserver to be up and running # executes step configure-geoserver from task.py file nohup sh -c "invoke configure-geoserver" & diff --git a/docker/geoserver/set_geoserver_auth.sh b/docker/geoserver/set_geoserver_auth.sh index 023636d..9a71512 100644 --- a/docker/geoserver/set_geoserver_auth.sh +++ b/docker/geoserver/set_geoserver_auth.sh @@ -3,11 +3,17 @@ auth_conf_source="$1" auth_conf_target="$2" # Creating a temporary file for sed to write the changes to -temp_file="xml.tmp" -touch $temp_file +# Use mktemp so the temp file lands in a writable location ($TMPDIR, default +# /tmp) instead of the current working directory, which may not be writable +# when the image runs as a non-root user. +temp_file="$(mktemp)" +trap 'rm -f "$temp_file"' EXIT -source /root/.bashrc -source /root/.override_env +# Use "${GEOSERVER_HOME}" (default /root for backward compatibility) so the +# environment written by entrypoint.sh is picked up regardless of the home dir. +GEOSERVER_HOME="${GEOSERVER_HOME:-/root}" +[ -f "${GEOSERVER_HOME}/.bashrc" ] && source "${GEOSERVER_HOME}/.bashrc" +[ -f "${GEOSERVER_HOME}/.override_env" ] && source "${GEOSERVER_HOME}/.override_env" test -z "$auth_conf_source" && echo "You must specify a source file" && exit 1 test -z "$auth_conf_target" && echo "You must specify a target conf directory" && exit 1 @@ -74,12 +80,16 @@ do echo "DEBUG: Found the new value for the element <$i> - '$newvalue'" # Replacing element’s value with $SUBSTITUTION_URL # echo -ne "<$i>$tagvalue" | xmlstarlet sel -t -m "//a" -v . -n - sed -e "s@<$i>$tagvalue<\/$i>@<$i>$newvalue<\/$i>@g" "$auth_conf_source" > "$temp_file" - cp "$temp_file" "$auth_conf_source" + if ! sed -e "s@<$i>$tagvalue<\/$i>@<$i>$newvalue<\/$i>@g" "$auth_conf_source" > "$temp_file"; then + echo "ERROR: failed to write temporary file '$temp_file'" >&2 + exit 1 + fi + if ! cp "$temp_file" "$auth_conf_source"; then + echo "ERROR: failed to write back to '$auth_conf_source'" >&2 + exit 1 + fi + done -# Writing our changes back to the original file ($auth_conf_source) -# no longer needed -# mv $temp_file $auth_conf_source echo "DEBUG: Finished... [Ok] --- Final xml file is \n" cat "$auth_conf_source"