diff --git a/Dockerfile b/Dockerfile index 29231f363..b37a9d913 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,6 +61,9 @@ ENV \ # Add configuration scripts COPY guacamole-docker/bin/ /opt/guacamole/bin/ +COPY guacamole-docker/build.d/ /opt/guacamole/build.d/ +COPY guacamole-docker/entrypoint.d/ /opt/guacamole/entrypoint.d/ +COPY guacamole-docker/environment/ /opt/guacamole/environment/ # Copy source to container for sake of build COPY . "$BUILD_DIR" @@ -68,6 +71,8 @@ COPY . "$BUILD_DIR" # Run the build itself RUN /opt/guacamole/bin/build-guacamole.sh "$BUILD_DIR" /opt/guacamole +RUN rm -rf /opt/guacamole/build.d /opt/guacamole/bin/build-guacamole.sh + # For the runtime image, we start with the official Tomcat distribution FROM tomcat:${TOMCAT_VERSION}-${TOMCAT_JRE} @@ -91,6 +96,9 @@ RUN useradd --system --create-home --shell /usr/sbin/nologin --uid $UID --gid $G # Run with user guacamole USER guacamole +# Environment variable defaults +ENV GUACAMOLE_HOME=/etc/guacamole + # Start Guacamole under Tomcat, listening on 0.0.0.0:8080 EXPOSE 8080 -CMD ["/opt/guacamole/bin/start.sh" ] +CMD ["/opt/guacamole/bin/entrypoint.sh" ] diff --git a/guacamole-docker/bin/build-guacamole.sh b/guacamole-docker/bin/build-guacamole.sh index 2fc6c9582..595bd70d9 100755 --- a/guacamole-docker/bin/build-guacamole.sh +++ b/guacamole-docker/bin/build-guacamole.sh @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/bash -e # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -23,10 +23,15 @@ ## ## Builds Guacamole, saving "guacamole.war" and all applicable extension .jars ## using the guacamole-client source contained within the given directory. -## Extension files will be grouped by their associated type, with all MySQL -## files being placed within the "mysql/" subdirectory of the destination, all -## PostgreSQL files being placed within the "postgresql/" subdirectory of the -## destination, etc. +## Extension files will be grouped by their associated type, identical to +## extracting the .tar.gz files included with each Guacamole release except +## that version numbers are stripped from directory and .jar file names. +## +## The build process is split across multiple scripts within the +## /opt/guacamole/build.d directory. Additional steps may be added to the +## build process by adding .sh scripts to this directory. Any such scripts MUST +## be shell scripts ending with a ".sh" extension and MUST be written for bash +## (the shell used by this entrypoint). ## ## @param BUILD_DIR ## The directory which currently contains the guacamole-client source and @@ -39,164 +44,21 @@ ## extension type. ## +## +## The directory which currently contains the guacamole-client source and in +## which the build should be performed. +## BUILD_DIR="$1" + +## +## The directory to save guacamole.war within, along with all extension .jars. +## Note that this script will create extension-specific subdirectories within +## this directory, and files will thus be grouped by extension type. +## DESTINATION="$2" -# -# Create destination, if it does not yet exist -# +# Run all scripts within the "build.d" directory +for SCRIPT in /opt/guacamole/build.d/*.sh; do + source "$SCRIPT" +done -mkdir -p "$DESTINATION" - -# -# Build guacamole.war and all extensions -# - -cd "$BUILD_DIR" - -# -# Run the maven build, applying any arbitrary provided maven arguments. -# - -mvn $MAVEN_ARGUMENTS package - -# -# Copy guacamole.war to destination -# - -cp guacamole/target/*.war "$DESTINATION/guacamole.war" - -# -# Copy JDBC auth extensions and SQL scripts -# - -tar -xzf extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/target/*.tar.gz \ - -C "$DESTINATION" \ - --wildcards \ - --no-anchored \ - --strip-components=1 \ - "*.jar" \ - "*.sql" - -# -# Download MySQL JDBC driver -# - -echo "Downloading MySQL Connector/J ..." -curl -L "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-j-$MYSQL_JDBC_VERSION.tar.gz" | \ -tar -xz \ - -C "$DESTINATION/mysql/" \ - --wildcards \ - --no-anchored \ - --no-wildcards-match-slash \ - --strip-components=1 \ - "mysql-connector-*.jar" - -# -# Download PostgreSQL JDBC driver -# - -echo "Downloading PostgreSQL JDBC driver ..." -curl -L "https://jdbc.postgresql.org/download/postgresql-$PGSQL_JDBC_VERSION.jar" \ - > "$DESTINATION/postgresql/postgresql-$PGSQL_JDBC_VERSION.jar" - -# -# Copy SSO auth extensions -# - -tar -xzf extensions/guacamole-auth-sso/modules/guacamole-auth-sso-dist/target/*.tar.gz \ - -C "$DESTINATION" \ - --wildcards \ - --no-anchored \ - --strip-components=1 \ - "*.jar" - -# -# Download SQL Server JDBC driver -# - -echo "Downloading SQL Server JDBC driver ..." -curl -L "https://github.com/microsoft/mssql-jdbc/releases/download/v$MSSQL_JDBC_VERSION/mssql-jdbc-$MSSQL_JDBC_VERSION.jre8.jar" \ - > "$DESTINATION/sqlserver/mssql-jdbc-$MSSQL_JDBC_VERSION.jre8.jar" \ - -# -# Copy LDAP auth extension and schema modifications -# - -mkdir -p "$DESTINATION/ldap" -tar -xzf extensions/guacamole-auth-ldap/target/*.tar.gz \ - -C "$DESTINATION/ldap" \ - --wildcards \ - --no-anchored \ - --xform="s#.*/##" \ - "*.jar" \ - "*.ldif" - -# -# Copy Radius auth extension if it was build -# - -if [ -f extensions/guacamole-auth-radius/target/guacamole-auth-radius*.jar ]; then - mkdir -p "$DESTINATION/radius" - cp extensions/guacamole-auth-radius/target/guacamole-auth-radius*.jar "$DESTINATION/radius" -fi - -# -# Copy TOTP auth extension if it was built -# - -if [ -f extensions/guacamole-auth-totp/target/guacamole-auth-totp*.jar ]; then - mkdir -p "$DESTINATION/totp" - cp extensions/guacamole-auth-totp/target/guacamole-auth-totp*.jar "$DESTINATION/totp" -fi - -# -# Copy Duo auth extension if it was built -# - -if [ -f extensions/guacamole-auth-duo/target/*.tar.gz ]; then - mkdir -p "$DESTINATION/duo" - tar -xzf extensions/guacamole-auth-duo/target/*.tar.gz \ - -C "$DESTINATION/duo/" \ - --wildcards \ - --no-anchored \ - --no-wildcards-match-slash \ - --strip-components=1 \ - "*.jar" -fi - -# -# Copy header auth extension if it was built -# - -if [ -f extensions/guacamole-auth-header/target/guacamole-auth-header*.jar ]; then - mkdir -p "$DESTINATION/header" - cp extensions/guacamole-auth-header/target/guacamole-auth-header*.jar "$DESTINATION/header" -fi - -# -# Copy json auth extension if it was built -# - -if [ -f extensions/guacamole-auth-json/target/guacamole-auth-json*.jar ]; then - mkdir -p "$DESTINATION/json" - cp extensions/guacamole-auth-json/target/guacamole-auth-json*.jar "$DESTINATION/json" -fi - -# -# Copy automatic brute-force banning auth extension if it was built -# - -if [ -f extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar ]; then - mkdir -p "$DESTINATION/ban" - cp extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar "$DESTINATION/ban" -fi - -# -# Copy history recording storage extension if it was built -# - -if [ -f extensions/guacamole-history-recording-storage/target/guacamole-history-recording-storage*.jar ]; then - mkdir -p "$DESTINATION/recordings" - cp extensions/guacamole-history-recording-storage/target/guacamole-history-recording-storage*.jar "$DESTINATION/recordings" -fi diff --git a/guacamole-docker/bin/entrypoint.sh b/guacamole-docker/bin/entrypoint.sh new file mode 100755 index 000000000..509332f85 --- /dev/null +++ b/guacamole-docker/bin/entrypoint.sh @@ -0,0 +1,39 @@ +#!/bin/bash -e +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn entrypoint.sh +## +## (Re-)configures the Apache Guacamole web application based on the values of +## environment variables, deploys the web application beneath a bundled copy of +## Apache Tomcat, and starts Tomcat. +## +## The startup process is split across multiple scripts within the +## /opt/guacamole/entrypoint.d directory. Additional steps may be added to the +## startup process by adding .sh scripts to this directory. Any such scripts +## MUST be shell scripts ending with a ".sh" extension and MUST be written for +## bash (the shell used by this entrypoint). +## + +# Run all scripts within the "entrypoint.d" directory +for SCRIPT in /opt/guacamole/entrypoint.d/*.sh; do + source "$SCRIPT" +done + diff --git a/guacamole-docker/bin/start.sh b/guacamole-docker/bin/start.sh deleted file mode 100755 index 4588c0eb4..000000000 --- a/guacamole-docker/bin/start.sh +++ /dev/null @@ -1,1251 +0,0 @@ -#!/bin/bash -e -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -## -## @fn start.sh -## -## Automatically configures and starts Guacamole under Tomcat. Guacamole's -## guacamole.properties file will be automatically generated based on the -## linked database container (either MySQL, PostgreSQL or SQLServer) and the linked guacd -## container. The Tomcat process will ultimately replace the process of this -## script, running in the foreground until terminated. -## - -GUACAMOLE_HOME_TEMPLATE="$GUACAMOLE_HOME" - -GUACAMOLE_HOME="$HOME/.guacamole" -GUACAMOLE_EXT="$GUACAMOLE_HOME/extensions" -GUACAMOLE_LIB="$GUACAMOLE_HOME/lib" -GUACAMOLE_PROPERTIES="$GUACAMOLE_HOME/guacamole.properties" - -## -## Sets the given property to the given value within guacamole.properties, -## creating guacamole.properties first if necessary. -## -## @param NAME -## The name of the property to set. -## -## @param VALUE -## The value to set the property to. -## -set_property() { - - NAME="$1" - VALUE="$2" - - # Ensure guacamole.properties exists - if [ ! -e "$GUACAMOLE_PROPERTIES" ]; then - mkdir -p "$GUACAMOLE_HOME" - echo "# guacamole.properties - generated `date`" > "$GUACAMOLE_PROPERTIES" - fi - - # Set property - echo "$NAME: $VALUE" >> "$GUACAMOLE_PROPERTIES" - -} - -## -## Sets the given property to the given value within guacamole.properties only -## if a value is provided, creating guacamole.properties first if necessary. -## -## @param NAME -## The name of the property to set. -## -## @param VALUE -## The value to set the property to, if any. If omitted or empty, the -## property will not be set. -## -set_optional_property() { - - NAME="$1" - VALUE="$2" - - # Set the property only if a value is provided - if [ -n "$VALUE" ]; then - set_property "$NAME" "$VALUE" - fi - -} - -# Print error message regarding missing required variables for MySQL authentication -mysql_missing_vars() { - cat < element - xmlstarlet edit --inplace \ - --insert '/Server/Service/Engine/Host/*' --type elem -n Valve \ - --insert '/Server/Service/Engine/Host/Valve[not(@className)]' --type attr -n className -v org.apache.catalina.valves.RemoteIpValve \ - $CATALINA_BASE/conf/server.xml - - # Allowed IPs - if [ -z "$PROXY_ALLOWED_IPS_REGEX" ]; then - echo "Using default Tomcat allowed IPs regex" - else - xmlstarlet edit --inplace \ - --insert '/Server/Service/Engine/Host/Valve[@className="org.apache.catalina.valves.RemoteIpValve"]' \ - --type attr -n internalProxies -v "$PROXY_ALLOWED_IPS_REGEX" \ - $CATALINA_BASE/conf/server.xml - fi - - # X-Forwarded-For - if [ -z "$PROXY_IP_HEADER" ]; then - echo "Using default Tomcat proxy IP header" - else - xmlstarlet edit --inplace \ - --insert "/Server/Service/Engine/Host/Valve[@className='org.apache.catalina.valves.RemoteIpValve']" \ - --type attr -n remoteIpHeader -v "$PROXY_IP_HEADER" \ - $CATALINA_BASE/conf/server.xml - fi - - # X-Forwarded-Proto - if [ -z "$PROXY_PROTOCOL_HEADER" ]; then - echo "Using default Tomcat proxy protocol header" - else - xmlstarlet edit --inplace \ - --insert "/Server/Service/Engine/Host/Valve[@className='org.apache.catalina.valves.RemoteIpValve']" \ - --type attr -n protocolHeader -v "$PROXY_PROTOCOL_HEADER" \ - $CATALINA_BASE/conf/server.xml - fi - - # X-Forwarded-By - if [ -z "$PROXY_BY_HEADER" ]; then - echo "Using default Tomcat proxy forwarded by header" - else - xmlstarlet edit --inplace \ - --insert "/Server/Service/Engine/Host/Valve[@className='org.apache.catalina.valves.RemoteIpValve']" \ - --type attr -n remoteIpProxiesHeader -v "$PROXY_BY_HEADER" \ - $CATALINA_BASE/conf/server.xml - fi -} - -## -## Adds api-session-timeout to guacamole.properties -## -associate_apisessiontimeout() { - set_optional_property "api-session-timeout" "$API_SESSION_TIMEOUT" -} - -## -## Starts Guacamole under Tomcat, replacing the current process with the -## Tomcat process. As the current process will be replaced, this MUST be the -## last function run within the script. -## -start_guacamole() { - - # User-only writable CATALINA_BASE - export CATALINA_BASE=$HOME/tomcat - for dir in logs temp webapps work; do - mkdir -p $CATALINA_BASE/$dir - done - cp -R /usr/local/tomcat/conf $CATALINA_BASE - - # Set up Tomcat RemoteIPValve - if [ "$REMOTE_IP_VALVE_ENABLED" = "true" ]; then - enable_remote_ip_valve - fi - - # Install webapp - ln -sf /opt/guacamole/guacamole.war $CATALINA_BASE/webapps/${WEBAPP_CONTEXT:-guacamole}.war - - # Start tomcat - cd /usr/local/tomcat - exec catalina.sh run - -} - -# -# Start with a fresh GUACAMOLE_HOME -# - -rm -Rf "$GUACAMOLE_HOME" - -# -# Copy contents of provided GUACAMOLE_HOME template, if any -# - -if [ -n "$GUACAMOLE_HOME_TEMPLATE" ]; then - cp -a "$GUACAMOLE_HOME_TEMPLATE/." "$GUACAMOLE_HOME/" -fi - -# -# Create and define Guacamole lib and extensions directories -# - -mkdir -p "$GUACAMOLE_EXT" -mkdir -p "$GUACAMOLE_LIB" - -# -# Point to associated guacd -# - -# Use linked container for guacd if specified -if [ -n "$GUACD_NAME" ]; then - GUACD_HOSTNAME="$GUACD_PORT_4822_TCP_ADDR" - GUACD_PORT="$GUACD_PORT_4822_TCP_PORT" -fi - -# Use default guacd port if none specified -GUACD_PORT="${GUACD_PORT-4822}" - -# Verify required guacd connection information is present -if [ -z "$GUACD_HOSTNAME" -o -z "$GUACD_PORT" ]; then - cat < $VAR_PREFIX" + mkdir -p "$DESTINATION/environment/$VAR_PREFIX/extensions" + ln -s "$DESTINATION/extensions/$EXT_PATH"/*.jar "$DESTINATION/environment/$VAR_PREFIX/extensions/" + else + echo "Skipped: $EXT_PATH (not built)" + fi + + done + +} + +# +# This section is a mapping of all bundled extensions to their corresponding +# variable prefixes. Each line consists of a whitespace-separated pair of +# extension path (the relative directory containing the .jar file) to that +# extension's variable prefix. For readability, a period may be used in lieu of +# a space. +# +# NOTES: +# +# (1) The actual variables used by each extension are not determined here, but +# rather by the transformation of their configuration properties to variables +# ("lowercase-with-dashes" to "UPPERCASE_WITH_UNDERSCORES"). The variable +# prefixes listed here should be chosen to match the prefixes resulting from +# that transformation of the extensions' properties. +# +# (2) The paths on the left side of this mapping are the paths of the extension +# .jar files relative to the "/opt/guacamole/extensions" directory used by the +# container to store extensions prior to use. They are identical to the paths +# used by the distribution .tar.gz files provided with each Guacamole release, +# except that the version numbers have been stripped from the top-level path. +# +# (3) The script processing this file uses these prefixes to define and process +# an additional "ENABLED" variable (ie: "BAN_ENABLED", "TOTP_ENABLED", etc.) +# that can be used to enable/disable an extension entirely regardless of the +# presence/absence of other variables with the prefix. This allows extensions +# that need no configuration to be easily enabled. It also allows extensions +# that already have configuration present to be easily disabled without +# requiring that all other configuration be removed. +# +map_extensions <<'EOF' + guacamole-auth-ban..........................BAN_ + guacamole-auth-duo..........................DUO_ + guacamole-auth-header.......................HTTP_AUTH_ + guacamole-auth-jdbc/mysql...................MYSQL_ + guacamole-auth-jdbc/postgresql..............POSTGRESQL_ + guacamole-auth-jdbc/sqlserver...............SQLSERVER_ + guacamole-auth-json.........................JSON_ + guacamole-auth-ldap.........................LDAP_ + guacamole-auth-quickconnect.................QUICKCONNECT_ + guacamole-auth-radius.......................RADIUS_ + guacamole-auth-sso/cas......................CAS_ + guacamole-auth-sso/openid...................OPENID_ + guacamole-auth-sso/saml.....................SAML_ + guacamole-auth-sso/ssl......................SSL_ + guacamole-auth-totp.........................TOTP_ + guacamole-display-statistics................DISPLAY_STATISTICS_ + guacamole-history-recording-storage.........RECORDING_ + guacamole-vault/ksm.........................KSM_ +EOF + diff --git a/guacamole-docker/build.d/020-download-drivers.sh b/guacamole-docker/build.d/020-download-drivers.sh new file mode 100644 index 000000000..6613dc3a2 --- /dev/null +++ b/guacamole-docker/build.d/020-download-drivers.sh @@ -0,0 +1,99 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn 030-download-drivers.sh +## +## Downloads all JDBC drivers required by the various supported databases. Each +## downloaded driver is stored beneath /opt/guacamole/drivers, with symbolic +## links added to the mappings beneath /opt/guacamole/environment to ensure any +## required drivers are added to GUACAMOLE_HOME if necessary to support a +## requested database. +## + +## +## Downloads the JDBC driver at the given URL, storing the driver's .jar file +## under the given name and environment variable prefix. The downloaded .jar +## file is stored such that it is pulled into GUACAMOLE_HOME automatically if +## environment variables with that prefix are used. +## +## If the URL is for a .tar.gz file and not a .jar file, the .jar will be +## automatically extracted from the .tar.gz as it is downloaded. +## +## @param VAR_PREFIX +## The environment variable prefix used by the extension that requires the +## driver. +## +## @param URL +## The URL that the driver should be downloaded from. +## +## @param DEST_JAR +## The filename to assign to the downloaded .jar file. This is mainly +## needed to ensure that the drivers bundled with the image have names that +## are predictable and reliable enough that they can be consumed by +## third-party use of this image. +## +download_driver() { + + local VAR_PREFIX="$1" + local URL="$2" + local DEST_JAR="$3" + + # Ensure primary destination path for .jar file exists + local DEST_PATH="$DESTINATION/drivers/" + mkdir -p "$DEST_PATH" + + # Download requested .jar file, extracting from .tar.gz if necessary + if [[ "$URL" == *.tar.gz ]]; then + curl -L "$URL" | tar -xz \ + --wildcards \ + --no-anchored \ + --no-wildcards-match-slash \ + --to-stdout \ + "*.jar" > "$DEST_PATH/$DEST_JAR" + else + curl -L "$URL" > "$DEST_PATH/$DEST_JAR" + fi + + # Add any required link to ensure the .jar file is loaded along with the + # extension that requires it + mkdir -p "$DESTINATION/environment/$VAR_PREFIX/lib" + ln -s "$DEST_PATH/$DEST_JAR" "$DESTINATION/environment/$VAR_PREFIX/lib/" + +} + +# +# Download and link any required JDBC drivers +# + +# MySQL JDBC driver +download_driver "MYSQL_" \ + "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-j-$MYSQL_JDBC_VERSION.tar.gz" \ + "mysql-jdbc.jar" + +# PostgreSQL JDBC driver +download_driver "POSTGRESQL_" \ + "https://jdbc.postgresql.org/download/postgresql-$PGSQL_JDBC_VERSION.jar" \ + "postgresql-jdbc.jar" + +# SQL Server JDBC driver +download_driver "SQLSERVER_" \ + "https://github.com/microsoft/mssql-jdbc/releases/download/v$MSSQL_JDBC_VERSION/mssql-jdbc-$MSSQL_JDBC_VERSION.jre8.jar" \ + "mssql-jdbc.jar" + diff --git a/guacamole-docker/build.d/999-verify-sanity.sh b/guacamole-docker/build.d/999-verify-sanity.sh new file mode 100644 index 000000000..48707da86 --- /dev/null +++ b/guacamole-docker/build.d/999-verify-sanity.sh @@ -0,0 +1,47 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn 999-verify-sanity.sh +## +## Performs sanity checks on the results of the build that verify the image +## contains everything it is expected to contain, including all built +## extensions. If symbolic links were not correctly constructed, or some built +## extensions were not mapped to environment variable prefixes, this script +## will log errors and fail the build. +## + +# Perform basic sanity checks that the symbolic links used to associated +# environment variables with extensions/libraries have been correctly created, +# bailing out if any problems are found. +( + + # Search for any broken symbolic links intended to map files for + # environment variables + find "$DESTINATION/environment/" -xtype l | sed 's/^/Broken link: /' + + # Search for extensions that have not been mapped to any environment + # variables at all + comm -23 \ + <(find "$DESTINATION/extensions/" -name "*.jar" -exec realpath "{}" ";" | sort -u) \ + <(find "$DESTINATION/environment/" -path "**/extensions/*.jar" -exec realpath "{}" ";" | sort -u) \ + | sed 's/^/Unmapped extension: /' + +) | sed 's/^/ERROR: /' | (! grep .) >&2 || exit 1 + diff --git a/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh b/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh new file mode 100644 index 000000000..077fb13ad --- /dev/null +++ b/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh @@ -0,0 +1,105 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn 000-migrate-legacy-variables.sh +## +## Checks for usage of any environment variables that were formerly supported +## but are now deprecated, warning when any deprecated variables are +## encountered. Until support for a deprecated variable is entirely removed, +## the value provided for the deprecated variable is automatically assigned to +## the currently-supported variable. +## + +## +## Checks for usage of the given deprecated environment variable, automatically +## assigning its value to the given currently-supported environment variable. +## If usage of the deprecated variable is found, a warning is printed to +## STDERR. +## +## @param LEGACY_VAR_NAME +## The name of the environment variable that's deprecated. +## +## @param CURRENT_VAR_NAME +## The name of the environment variable that is currently supported and +## replaces the deprecated variable. +## +deprecate_variable() { + + local LEGACY_VAR_NAME="$1" + local CURRENT_VAR_NAME="$2" + + if [ -n "${!LEGACY_VAR_NAME}" ]; then + echo "WARNING: The \"$LEGACY_VAR_NAME\" environment variable has been deprecated in favor of \"$CURRENT_VAR_NAME\". Please migrate your configuration when possible, as support for the older name may be removed in future releases." >&2 + export "$CURRENT_VAR_NAME"="${!LEGACY_VAR_NAME}" + fi + +} + +## +## Checks for usage of any environment variables using the given deprecated +## prefix, automatically assigning their values to corresponding environment +## variables having the given currently-supported prefix. If usage of the +## deprecated prefix is found, a warning is printed to STDERR. +## +## @param LEGACY_VAR_PREFIX +## The environment variable prefix that's deprecated. +## +## @param CURRENT_VAR_PREFIX +## The environment variable prefix that is currently supported and +## replaces the deprecated variable prefix. +## +deprecate_variable_prefix() { + + local LEGACY_VAR_PREFIX="$1" + local CURRENT_VAR_PREFIX="$2" + + local LEGACY_VAR_NAME + local CURRENT_VAR_NAME + local HAS_LEGACY_VARIABLES=0 + + # Automatically reassign all "POSTGRES_*" variables to "POSTGRESQL_*" + while read -r LEGACY_VAR_NAME; do + HAS_LEGACY_VARIABLES=1 + CURRENT_VAR_NAME="$CURRENT_VAR_PREFIX${LEGACY_VAR_NAME#$LEGACY_VAR_PREFIX}" + export "$CURRENT_VAR_NAME"="${!LEGACY_VAR_NAME}" + unset "$LEGACY_VAR_NAME" + done < <(awk 'BEGIN{for(v in ENVIRON) print v}' | grep "^$LEGACY_VAR_PREFIX") + + if [ "$HAS_LEGACY_VARIABLES" = "1" ]; then + echo "WARNING: The \"$LEGACY_VAR_PREFIX\" prefix for environment variables has been deprecated in favor of the \"$CURRENT_VAR_PREFIX\" prefix. Please migrate your configuration when possible, as support for the older prefix may be removed in future releases." >&2 + export "$CURRENT_VAR_NAME"="$LEGACY_VAR_NAME" + fi + +} + +# The old "*_USER" style for configuring the user account to be used to access +# the database is being replaced with "*_USERNAME" such that all environment +# variables exactly correspond to the names of configuration properties from +# guacamole.properties. +deprecate_variable "MYSQL_USER" "MYSQL_USERNAME" +deprecate_variable "POSTGRES_USER" "POSTGRESQL_USERNAME" +deprecate_variable "SQLSERVER_USER" "SQLSERVER_USERNAME" + +# The old "POSTGRES_" prefix for configuring usage of PostgreSQL is being +# replaced with "POSTGRESQL_" such that all environment variables exactly +# correspond to the names of configuration properties from +# guacamole.properties. +deprecate_variable_prefix "POSTGRES_" "POSTGRESQL_" + diff --git a/guacamole-docker/entrypoint.d/100-generate-guacamole-home.sh b/guacamole-docker/entrypoint.d/100-generate-guacamole-home.sh new file mode 100644 index 000000000..1db4d6086 --- /dev/null +++ b/guacamole-docker/entrypoint.d/100-generate-guacamole-home.sh @@ -0,0 +1,111 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn 010-generate-guacamole-home.sh +## +## Automatically generates a temporary, skeleton GUACAMOLE_HOME to be used for +## this run of the container. GUACAMOLE_HOMEs from previous runs are +## automatically deleted prior to creating the new skeleton. A +## randomly-generated temporary directory is used instead of a standard +## directory like "/etc/guacamole" to allow users to use "/etc/guacamole" as a +## basis for their own configuration. +## + +## +## The directory to copy/link over as a basis for the GUACAMOLE_HOME actually +## used by the Guacamole web application. Any configuration generated by this +## container will be overlaid on top of this configuration. To achieve the +## overlay, symbolic links will be created for all files inside and beneath +## this directory. Only the guacamole.properties file will be copied instead of +## using symbolic links (to ensure property generation performed by the +## container does not potentially modify an external file). +## +GUACAMOLE_HOME_TEMPLATE="$GUACAMOLE_HOME" + +## +## Tests whether a given property is set within the guacamole.properties file +## in GUACAMOLE_HOME. +## +## @param PROPERTY_NAME +## The name of the property to check. +## +## @returns +## Zero if the given property is set to any value within +## guacamole.properties, non-zero otherwise. +## +is_property_set() { + local PROPERTY_NAME="$1" + grep "^[[:space:]]*$PROPERTY_NAME\>" "$GUACAMOLE_HOME/guacamole.properties" &> /dev/null +} + +# +# Start with a fresh GUACAMOLE_HOME +# + +rm -rf /tmp/guacamole-home.* +GUACAMOLE_HOME="`mktemp -p /tmp -d guacamole-home.XXXXXXXXXX`" +mkdir -p "$GUACAMOLE_HOME/"{lib,extensions} + +cat > "$GUACAMOLE_HOME/guacamole.properties" <> "$GUACAMOLE_HOME/guacamole.properties" + fi + +fi + +# Enable reading of properties directly from environment variables unless +# overridden +if ! is_property_set "enable-environment-properties"; then + cat >> "$GUACAMOLE_HOME/guacamole.properties" <<'EOF' +# +# NOTE: The following was automatically added by the container entrypoint to +# allow all Guacamole configuration properties to be automatically read from +# environment variables. If this is not desired, you can override this behavior +# by specifying the "enable-environment-properties" variable yourself in your +# own guacamole.properties file. +# +enable-environment-properties: true +EOF +fi + diff --git a/guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh b/guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh new file mode 100644 index 000000000..70a099976 --- /dev/null +++ b/guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn 030-configure-guacamole-logging.sh +## +## Checks the value of the LOGBACK_LEVEL environment variable, producing a +## corresponding logback.xml file within GUACAMOLE_HOME if a log level has been +## explicitly specified. +## + +# Set logback level if specified +if [ -n "$LOGBACK_LEVEL" ]; then + unzip -o -j /opt/guacamole/guacamole.war WEB-INF/classes/logback.xml -d $GUACAMOLE_HOME + sed -i "s/level=\"info\"/level=\"$LOGBACK_LEVEL\"/" $GUACAMOLE_HOME/logback.xml +fi + diff --git a/guacamole-docker/entrypoint.d/500-generate-tomcat-catalina-base.sh b/guacamole-docker/entrypoint.d/500-generate-tomcat-catalina-base.sh new file mode 100644 index 000000000..cd869511f --- /dev/null +++ b/guacamole-docker/entrypoint.d/500-generate-tomcat-catalina-base.sh @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## 500-generate-tomcat-catalina-base.sh +## +## Automcatically generates a fresh, temporary CATALINA_BASE for Apache Tomcat. +## This allows Tomcat to run as a reduced-privilege user, and allows its +## configuration to be dynamically generated by the container entrypoint at +## startup. +## + +# +# Start with a fresh CATALINA_BASE +# + +rm -rf /tmp/catalina-base.* +export CATALINA_BASE="`mktemp -p /tmp -d catalina-base.XXXXXXXXXX`" + +# User-only writable CATALINA_BASE +for dir in logs temp webapps work; do + mkdir -p $CATALINA_BASE/$dir +done +cp -R /usr/local/tomcat/conf $CATALINA_BASE + +cat >> "$CATALINA_BASE/conf/catalina.properties" < /dev/null + +} + +# Search environment for enabled extensions/features based on environment +# variable prefixes +for VAR_BASE in /opt/guacamole/environment/*; do + + # Skip any directories without at least one corresponding environment + # variable set + is_feature_enabled "$(basename "$VAR_BASE")" || continue + + # Execute any associated configuration script + [ ! -e "$VAR_BASE/configure.sh" ] || source "$VAR_BASE/configure.sh" + + # Add any required links for extensions/libraries associated with the + # configured extension + for SUBDIR in lib extensions; do + if [ -d "$VAR_BASE/$SUBDIR" ]; then + mkdir -p "$GUACAMOLE_HOME/$SUBDIR/" + ln -s "$VAR_BASE/$SUBDIR"/* "$GUACAMOLE_HOME/$SUBDIR/" + fi + done + +done + diff --git a/guacamole-docker/entrypoint.d/999-start-tomcat.sh b/guacamole-docker/entrypoint.d/999-start-tomcat.sh new file mode 100644 index 000000000..e31b4e3ed --- /dev/null +++ b/guacamole-docker/entrypoint.d/999-start-tomcat.sh @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn 999-start-tomcat.sh +## +## Starts Tomcat. This script replaces the current process with the Tomcat +## process and does not exit. +## + +# Start tomcat +cd /usr/local/tomcat +exec catalina.sh run + diff --git a/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh b/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh new file mode 100644 index 000000000..55596ad28 --- /dev/null +++ b/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh @@ -0,0 +1,72 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +## +## @fn REMOTE_IP_VALVE_/configure.sh +## +## Configures Tomcat to forward the IP addresses of clients behind a proxy if +## the REMOTE_IP_VALVE_ENABLED environment variable is set to "true". +## + +# Add element +xmlstarlet edit --inplace \ + --insert '/Server/Service/Engine/Host/*' --type elem -n Valve \ + --insert '/Server/Service/Engine/Host/Valve[not(@className)]' --type attr -n className -v org.apache.catalina.valves.RemoteIpValve \ + $CATALINA_BASE/conf/server.xml + +# Allowed IPs +if [ -z "$PROXY_ALLOWED_IPS_REGEX" ]; then + echo "Using default Tomcat allowed IPs regex" +else + xmlstarlet edit --inplace \ + --insert '/Server/Service/Engine/Host/Valve[@className="org.apache.catalina.valves.RemoteIpValve"]' \ + --type attr -n internalProxies -v "$PROXY_ALLOWED_IPS_REGEX" \ + $CATALINA_BASE/conf/server.xml +fi + +# X-Forwarded-For +if [ -z "$PROXY_IP_HEADER" ]; then + echo "Using default Tomcat proxy IP header" +else + xmlstarlet edit --inplace \ + --insert "/Server/Service/Engine/Host/Valve[@className='org.apache.catalina.valves.RemoteIpValve']" \ + --type attr -n remoteIpHeader -v "$PROXY_IP_HEADER" \ + $CATALINA_BASE/conf/server.xml +fi + +# X-Forwarded-Proto +if [ -z "$PROXY_PROTOCOL_HEADER" ]; then + echo "Using default Tomcat proxy protocol header" +else + xmlstarlet edit --inplace \ + --insert "/Server/Service/Engine/Host/Valve[@className='org.apache.catalina.valves.RemoteIpValve']" \ + --type attr -n protocolHeader -v "$PROXY_PROTOCOL_HEADER" \ + $CATALINA_BASE/conf/server.xml +fi + +# X-Forwarded-By +if [ -z "$PROXY_BY_HEADER" ]; then + echo "Using default Tomcat proxy forwarded by header" +else + xmlstarlet edit --inplace \ + --insert "/Server/Service/Engine/Host/Valve[@className='org.apache.catalina.valves.RemoteIpValve']" \ + --type attr -n remoteIpProxiesHeader -v "$PROXY_BY_HEADER" \ + $CATALINA_BASE/conf/server.xml +fi +