From 9580dd4f82daf10adcdc89fd25155c78c90a68bd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 18 Feb 2024 00:43:21 -0800 Subject: [PATCH 1/6] GUACAMOLE-374: Support absolutely all properties and extensions. --- Dockerfile | 10 +- guacamole-docker/bin/build-guacamole.sh | 186 +-- guacamole-docker/bin/entrypoint.sh | 39 + guacamole-docker/bin/start.sh | 1251 ----------------- .../000-build-and-install-guacamole.sh | 62 + .../build.d/010-map-guacamole-extensions.sh | 118 ++ .../build.d/020-download-drivers.sh | 99 ++ guacamole-docker/build.d/999-verify-sanity.sh | 47 + .../000-migrate-legacy-variables.sh | 105 ++ .../100-generate-guacamole-home.sh | 111 ++ .../110-configure-guacamole-logging.sh | 33 + .../500-generate-tomcat-catalina-base.sh | 50 + .../entrypoint.d/700-configure-features.sh | 88 ++ .../entrypoint.d/999-start-tomcat.sh | 30 + .../environment/REMOTE_IP_VALVE_/configure.sh | 72 + 15 files changed, 887 insertions(+), 1414 deletions(-) create mode 100755 guacamole-docker/bin/entrypoint.sh delete mode 100755 guacamole-docker/bin/start.sh create mode 100644 guacamole-docker/build.d/000-build-and-install-guacamole.sh create mode 100644 guacamole-docker/build.d/010-map-guacamole-extensions.sh create mode 100644 guacamole-docker/build.d/020-download-drivers.sh create mode 100644 guacamole-docker/build.d/999-verify-sanity.sh create mode 100644 guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh create mode 100644 guacamole-docker/entrypoint.d/100-generate-guacamole-home.sh create mode 100644 guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh create mode 100644 guacamole-docker/entrypoint.d/500-generate-tomcat-catalina-base.sh create mode 100644 guacamole-docker/entrypoint.d/700-configure-features.sh create mode 100644 guacamole-docker/entrypoint.d/999-start-tomcat.sh create mode 100644 guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh diff --git a/Dockerfile b/Dockerfile index 29231f3635..b37a9d9136 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 2fc6c95827..595bd70d9b 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" -DESTINATION="$2" - -# -# Create destination, if it does not yet exist -# - -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 +## +## 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" -# -# Copy history recording storage extension if it was built -# +# Run all scripts within the "build.d" directory +for SCRIPT in /opt/guacamole/build.d/*.sh; do + source "$SCRIPT" +done -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 0000000000..509332f850 --- /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 4588c0eb4a..0000000000 --- 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 0000000000..6613dc3a26 --- /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 0000000000..48707da86d --- /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 0000000000..077fb13ad0 --- /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 0000000000..1db4d60867 --- /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 0000000000..70a099976c --- /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 0000000000..cd869511f2 --- /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 0000000000..e31b4e3edc --- /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 0000000000..55596ad283 --- /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 + From d6a491f933d6aeb8ab4b055fa1a45708305f4373 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 18 Feb 2024 23:18:43 -0800 Subject: [PATCH 2/6] GUACAMOLE-374: Automatically read properties from files pointed to by "*_FILE" environment variables. --- Dockerfile | 3 +- .../GuacamoleServletContextListener.java | 32 +++++++++- ...temFileEnvironmentGuacamoleProperties.java | 64 +++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java diff --git a/Dockerfile b/Dockerfile index b37a9d9136..80aa578ec5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -97,7 +97,8 @@ RUN useradd --system --create-home --shell /usr/sbin/nologin --uid $UID --gid $G USER guacamole # Environment variable defaults -ENV GUACAMOLE_HOME=/etc/guacamole +ENV GUACAMOLE_HOME=/etc/guacamole \ + ENABLE_FILE_ENVIRONMENT_PROPERTIES=true # Start Guacamole under Tomcat, listening on 0.0.0.0:8080 EXPOSE 8080 diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java index 1068477c4a..df07662389 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java @@ -95,7 +95,7 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener /** * A property that determines whether environment variables are evaluated - * to override properties specified in guacamole.properties. + * to supply properties not specified in guacamole.properties. */ private static final BooleanGuacamoleProperty ENABLE_ENVIRONMENT_PROPERTIES = new BooleanGuacamoleProperty() { @@ -105,6 +105,19 @@ public String getName() { } }; + /** + * A property that determines whether environment variables of the form + * "*_FILE" are evaluated to supply properties not specified in + * guacamole.properties nor in environment variables. + */ + private static final BooleanGuacamoleProperty ENABLE_FILE_ENVIRONMENT_PROPERTIES = + new BooleanGuacamoleProperty() { + @Override + public String getName() { + return "enable-file-environment-properties"; + } + }; + /** * The Guacamole server environment. */ @@ -172,6 +185,23 @@ public void contextInitialized(ServletContextEvent servletContextEvent) { logger.debug("Error reading \"{}\" property from guacamole.properties.", ENABLE_ENVIRONMENT_PROPERTIES.getName(), e); } + // For any values not defined in GUACAMOLE_HOME/guacamole.properties + // nor in the system environment, read from files pointed to by + // corresponding "*_FILE" variables in the system environment if + // "enable-file-environment-properties" is set to "true" + try { + if (environment.getProperty(ENABLE_FILE_ENVIRONMENT_PROPERTIES, false)) { + environment.addGuacamoleProperties(new SystemFileEnvironmentGuacamoleProperties()); + logger.info("Additional configuration parameters may be read " + + "from files pointed to by \"*_FILE\" environment " + + "variables."); + } + } + catch (GuacamoleException e) { + logger.error("Unable to configure support for file environment properties: {}", e.getMessage()); + logger.debug("Error reading \"{}\" property from guacamole.properties.", ENABLE_FILE_ENVIRONMENT_PROPERTIES.getName(), e); + } + // Now that at least the main guacamole.properties source of // configuration information is available, initialize the session map sessionMap = new HashTokenSessionMap(environment); diff --git a/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java b/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java new file mode 100644 index 0000000000..8b08252925 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java @@ -0,0 +1,64 @@ +/* + * 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. + */ + +package org.apache.guacamole; + +import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.apache.guacamole.properties.GuacamoleProperties; +import org.apache.guacamole.token.TokenName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * GuacamoleProperties implementation which reads all properties from files + * whose filenames are stored in environment variables. The name of the + * environment variable corresponding to the filename is determined from the + * original property using {@link TokenName#canonicalize(java.lang.String)} + * with an additional "_FILE" suffix. + */ +public class SystemFileEnvironmentGuacamoleProperties implements GuacamoleProperties { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(SystemFileEnvironmentGuacamoleProperties.class); + + @Override + public String getProperty(String name) { + + String filename = System.getenv(TokenName.canonicalize(name) + "_FILE"); + if (filename != null) { + try { + return Files.asCharSource(new File(filename), StandardCharsets.UTF_8).read(); + } + catch (IOException e) { + logger.error("Property \"{}\" could not be read from file \"{}\": {}", name, filename, e.getMessage()); + logger.debug("Error reading property value from file.", e); + } + } + + return null; + + } + +} + From c818650ae0f59200fc3231333e7bb7857d310711 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 20 Feb 2024 23:55:06 -0800 Subject: [PATCH 3/6] GUACAMOLE-374: Switch to "REMOTE_IP_VALVE_*" environment variables for configuring RemoteIpValve. --- .../000-migrate-legacy-variables.sh | 8 +++ .../environment/REMOTE_IP_VALVE_/configure.sh | 71 ++++++++----------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh b/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh index 077fb13ad0..490827feeb 100644 --- a/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh +++ b/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh @@ -103,3 +103,11 @@ deprecate_variable "SQLSERVER_USER" "SQLSERVER_USERNAME" # guacamole.properties. deprecate_variable_prefix "POSTGRES_" "POSTGRESQL_" +# The old "PROXY_*" names for attributes supported by RemoteIpValve are being +# replaced with "REMOTE_IP_VALVE_*" attributes that more closely and +# predictably match their attribute names +deprecate_variable "PROXY_ALLOWED_IPS_REGEX" "REMOTE_IP_VALVE_INTERNAL_PROXIES" +deprecate_variable "PROXY_IP_HEADER" "REMOTE_IP_VALVE_REMOTE_IP_HEADER" +deprecate_variable "PROXY_PROTOCOL_HEADER" "REMOTE_IP_VALVE_PROTOCOL_HEADER" +# NOTE: PROXY_BY_HEADER never worked as there is no "remoteIpProxiesHeader" attribute for RemoteIpValve + diff --git a/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh b/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh index 55596ad283..ad6e5ffb5c 100644 --- a/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh +++ b/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh @@ -24,49 +24,38 @@ ## 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 +## +## Array of all xmlstarlet command-line options necessary to add the +## RemoteIpValve attributes that correspond to various "REMOTE_IP_VALVE_*" +## environment variables. +## +declare -a VALVE_ATTRIBUTES=( --type attr -n className -v org.apache.catalina.valves.RemoteIpValve ) -# 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 +# Translate all properties supported by RemoteIpValve into corresponding +# environment variables +for ATTRIBUTE in \ + remoteIpHeader \ + internalProxies \ + proxiesHeader \ + trustedProxies \ + protocolHeader \ + protocolHeaderHttpsValue \ + httpServerPort \ + httpsServerPort; do -# 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 + VAR_NAME="REMOTE_IP_VALVE_$(echo "$ATTRIBUTE" | sed 's/\([a-z]\)\([A-Z]\)/\1_\2/g' | tr 'a-z' 'A-Z')" + if [ -n "${!VAR_NAME}" ]; then + VALVE_ATTRIBUTES+=( --type attr -n "$ATTRIBUTE" -v "${!VAR_NAME}" ) + else + echo "Using default RemoteIpValve value for \"$ATTRIBUTE\" attribute." + 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 +done -# 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 +# Programmatically add requested RemoteIpValve entry +xmlstarlet edit --inplace \ + --insert '/Server/Service/Engine/Host/*' --type elem -n Valve \ + --insert '/Server/Service/Engine/Host/Valve[not(@className)]' \ + "${VALVE_ATTRIBUTES[@]}" \ + "$CATALINA_BASE/conf/server.xml" From acaee44a5353c5cab9f4ab738520f41ab89f2c1b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Apr 2024 15:40:34 -0700 Subject: [PATCH 4/6] GUACAMOLE-374: Allow multiple PropertyValue annotations to be associated with each enum value for EnumGuacamoleProperty. --- .../properties/EnumGuacamoleProperty.java | 40 ++++++++++++++++++- .../properties/EnumGuacamolePropertyTest.java | 4 +- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/properties/EnumGuacamoleProperty.java b/guacamole-ext/src/main/java/org/apache/guacamole/properties/EnumGuacamoleProperty.java index 255e4b09ab..a55299365f 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/properties/EnumGuacamoleProperty.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/properties/EnumGuacamoleProperty.java @@ -20,6 +20,7 @@ package org.apache.guacamole.properties; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -45,8 +46,10 @@ public abstract class EnumGuacamoleProperty> implements Guacam /** * Defines the string value which should be accepted and parsed into the - * annotated enum constant. + * annotated enum constant. This annotation is repeatable, and each enum + * constant may be associated with any any number of string values. */ + @Repeatable(PropertyValues.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public static @interface PropertyValue { @@ -63,6 +66,29 @@ public abstract class EnumGuacamoleProperty> implements Guacam } + /** + * Defines the string values which should be accepted and parsed into the + * annotated enum constant. Each enum constant may be associated with any + * any number of string values. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public static @interface PropertyValues { + + /** + * Returns the {@link PropertyValue} annotations that represent the + * String values that should produce the annotated enum constant when + * parsed. + * + * @return + * The {@link PropertyValue} annotations that represent the String + * values that should produce the annotated enum constant when + * parsed. + */ + PropertyValue[] value(); + + } + /** * Mapping of valid property values to the corresponding enum constants * that those values parse to. @@ -103,7 +129,17 @@ private static > Map getValueMapping(Class enumC + "match declared values.", e); } - // Map enum constant only if PropertyValue annotation is present + // Map enum constant only if one or more PropertyValue annotations + // are present + PropertyValues valuesAnnotation = field.getAnnotation(PropertyValues.class); + if (valuesAnnotation != null) { + for (PropertyValue valueAnnotation : valuesAnnotation.value()) + valueMapping.put(valueAnnotation.value(), value); + } + + // The PropertyValue annotation may appear as a separate, single + // annotation, or as a multi-valued annotation via PropertyValues + // (see above) PropertyValue valueAnnotation = field.getAnnotation(PropertyValue.class); if (valueAnnotation != null) valueMapping.put(valueAnnotation.value(), value); diff --git a/guacamole-ext/src/test/java/org/apache/guacamole/properties/EnumGuacamolePropertyTest.java b/guacamole-ext/src/test/java/org/apache/guacamole/properties/EnumGuacamolePropertyTest.java index 5b9a3d742b..f2f313c009 100644 --- a/guacamole-ext/src/test/java/org/apache/guacamole/properties/EnumGuacamolePropertyTest.java +++ b/guacamole-ext/src/test/java/org/apache/guacamole/properties/EnumGuacamolePropertyTest.java @@ -70,6 +70,7 @@ public static enum Fish { * @see Tuna (Wikipedia) */ @PropertyValue("tuna") + @PropertyValue("yellowfin") TUNA, /** @@ -135,6 +136,7 @@ public void testParseValue() throws GuacamoleException { assertEquals(Fish.TROUT, FAVORITE_FISH.parseValue("trout")); assertEquals(Fish.MACKEREL, FAVORITE_FISH.parseValue("mackerel")); assertEquals(Fish.TUNA, FAVORITE_FISH.parseValue("tuna")); + assertEquals(Fish.TUNA, FAVORITE_FISH.parseValue("yellowfin")); assertEquals(Fish.SARDINE, FAVORITE_FISH.parseValue("sardine")); } @@ -164,7 +166,7 @@ public void testParseInvalidValue() { } catch (GuacamoleException e) { String message = e.getMessage(); - assertTrue(message.contains("\"mackerel\", \"salmon\", \"sardine\", \"trout\", \"tuna\"")); + assertTrue(message.contains("\"mackerel\", \"salmon\", \"sardine\", \"trout\", \"tuna\", \"yellowfin\"")); } } From 649492b412823744be0263b26c2adf6e6e4dfcdb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 23 Apr 2024 10:11:16 -0700 Subject: [PATCH 5/6] GUACAMOLE-374: Allow log level to be configured easily with "log-level" property. --- Dockerfile | 4 +- .../000-migrate-legacy-variables.sh | 3 + .../110-configure-guacamole-logging.sh | 33 ---- .../GuacamoleServletContextListener.java | 25 ++- .../org/apache/guacamole/log/LogLevel.java | 163 ++++++++++++++++++ .../org/apache/guacamole/log/LogModule.java | 84 +++++++-- guacamole/src/main/resources/logback.xml | 34 ---- 7 files changed, 255 insertions(+), 91 deletions(-) delete mode 100644 guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh create mode 100644 guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java delete mode 100644 guacamole/src/main/resources/logback.xml diff --git a/Dockerfile b/Dockerfile index 80aa578ec5..8a3226acbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,9 +76,9 @@ 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} -# Install XMLStarlet for server.xml alterations and unzip for LOGBACK_LEVEL case +# Install XMLStarlet for server.xml alterations RUN apt-get update -qq \ - && apt-get install -y xmlstarlet unzip\ + && apt-get install -y xmlstarlet \ && rm -rf /var/lib/apt/lists/* # This is where the build artifacts go in the runtime image diff --git a/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh b/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh index 490827feeb..cb56c4c9ae 100644 --- a/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh +++ b/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh @@ -111,3 +111,6 @@ deprecate_variable "PROXY_IP_HEADER" "REMOTE_IP_VALVE_REMOTE_IP_HEADER" deprecate_variable "PROXY_PROTOCOL_HEADER" "REMOTE_IP_VALVE_PROTOCOL_HEADER" # NOTE: PROXY_BY_HEADER never worked as there is no "remoteIpProxiesHeader" attribute for RemoteIpValve +# The old "LOGBACK_LEVEL" environment variable has been replaced with +# "LOG_LEVEL" for consistency with the guacd image +deprecate_variable "LOGBACK_LEVEL" "LOG_LEVEL" diff --git a/guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh b/guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh deleted file mode 100644 index 70a099976c..0000000000 --- a/guacamole-docker/entrypoint.d/110-configure-guacamole-logging.sh +++ /dev/null @@ -1,33 +0,0 @@ -# -# 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/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java index df07662389..d8e3f3d581 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java @@ -240,13 +240,24 @@ protected Injector getInjector() { return current; // Create new injector if necessary - Injector injector = Guice.createInjector(Stage.PRODUCTION, - new EnvironmentModule(environment), - new LogModule(environment), - new ExtensionModule(environment), - new RESTServiceModule(sessionMap), - new TunnelModule() - ); + Injector injector = + + // Ensure environment and logging are configured FIRST ... + Guice.createInjector(Stage.PRODUCTION, + new EnvironmentModule(environment), + new LogModule(environment) + ) + + // ... before attempting configuration of any other modules + // (logging within the constructors of other modules may + // otherwise default to including messages from the "debug" + // level, regardless of how the application log level is + // actually configured) + .createChildInjector( + new ExtensionModule(environment), + new RESTServiceModule(sessionMap), + new TunnelModule() + ); return injector; diff --git a/guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java b/guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java new file mode 100644 index 0000000000..27bf0fe882 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java @@ -0,0 +1,163 @@ +/* + * 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. + */ + +package org.apache.guacamole.log; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; + +/** + * All log levels supported by the Apache Guacamole web application. Each log + * level describes a different level of verbosity for the log messages included + * in web application logs. + */ +public enum LogLevel { + + /** + * Errors that are fatal in the context of the operation being logged. + */ + @PropertyValue("error") + ERROR("error"), + + /** + * Non-fatal conditions that may indicate the presence of a problem. + */ + @PropertyValue("warning") + @PropertyValue("warn") + WARNING("warning"), + + /** + * Informational messages of general interest to users or administrators. + */ + @PropertyValue("info") + INFO("info"), + + /** + * Informational messages that are useful for debugging, but are generally + * not useful to users or administrators. It is expected that debug-level + * messages, while verbose, will not affect performance. + */ + @PropertyValue("debug") + DEBUG("debug"), + + /** + * Informational messages that may be useful for debugging, but which are + * so low-level that they may affect performance. + */ + @PropertyValue("trace") + TRACE("trace"); + + /** + * Format string whose sole format argument is a String containing the + * name of the log level. As this configuration will be fed to Logback, the + * name used must be a name acceptable by Logback. + */ + private static final String LOGBACK_XML_TEMPLATE = + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " %%d{HH:mm:ss.SSS} [%%thread] %%-5level %%logger{36} - %%msg%%n\n" + + " \n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n"; + + /** + * The name that should be used to refer to this log level in the context + * of configuring Guacamole. This name should be both descriptive and + * acceptable as the value of the "log-level" property. + */ + private final String canonicalName; + + /** + * The raw contents of the "logback.xml" that configures Logback to log + * messages at this level, encoded as UTF-8. + */ + private final byte[] logbackConfig; + + /** + * Creates a new LogLevel with the given names. The pair of names provided + * correspond to the name used within Guacamole's configuration and the + * name used within Logback's configuration. + * + * @param canonicalName + * The name that should be used for this log level when configuring + * Guacamole to log at this level using the "log-level" property. + * + * @param logbackLogLevel + * The name that would be provided to Logback to log at this level if + * manually configuring Logback using "logback.xml". + */ + private LogLevel(String canonicalName, String logbackLogLevel) { + this.canonicalName = canonicalName; + this.logbackConfig = String.format(LOGBACK_XML_TEMPLATE, logbackLogLevel).getBytes(StandardCharsets.UTF_8); + } + + /** + * Creates a new LogLevel with the given name. The provided name corresponds + * to both the name used within Guacamole's configuration and the name used + * within Logback's configuration. + * + * @param logLevel + * The name that should be used for this log level when configuring + * Guacamole to log at this level using the "log-level" property AND + * when manually configuring Logback to log at this level using a + * "logback.xml" configuration file. + */ + private LogLevel(String logLevel) { + this(logLevel, logLevel); + } + + /** + * Returns a name that may be used to refer to this log level when + * configuring Guacamole using the "log-level" property. + * + * @return + * A name that may be used to refer to this log level when + * configuring Guacamole using the "log-level" property. + */ + public String getCanonicalName() { + return canonicalName; + } + + /** + * Returns a new InputStream that streams the contents of an XML + * configuration file that can be provided to Logback to configure logging + * at this log level. + * + * @return + * A a new InputStream that streams the contents of an XML + * configuration file that can be provided to Logback to configure + * logging at this log level. + */ + public InputStream getLogbackConfiguration() { + return new ByteArrayInputStream(logbackConfig); + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java b/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java index accc4a9ddd..a9e4ac3bca 100644 --- a/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java @@ -25,7 +25,13 @@ import ch.qos.logback.core.util.StatusPrinter; import com.google.inject.AbstractModule; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +50,19 @@ public class LogModule extends AbstractModule { */ private final Environment environment; + /** + * Property that specifies the highest level of verbosity that Guacamole + * should use for the messages in its logs. + */ + private static final EnumGuacamoleProperty LOG_LEVEL = new EnumGuacamoleProperty(LogLevel.class) { + + @Override + public String getName() { + return "log-level"; + } + + }; + /** * Creates a new LogModule which uses the given environment to determine * the logging configuration. @@ -54,26 +73,57 @@ public class LogModule extends AbstractModule { public LogModule(Environment environment) { this.environment = environment; } - - @Override - protected void configure() { - // Only load logback configuration if GUACAMOLE_HOME exists - File guacamoleHome = environment.getGuacamoleHome(); - if (!guacamoleHome.isDirectory()) - return; + /** + * Returns an InputStream that streams the contents of the "logback.xml" + * file that Logback should read to configure logging to Guacamole. If the + * user provided their own "logback.xml" within GUACAMOLE_HOME, this will + * be an InputStream that reads the contents of that file. The required + * "logback.xml" will otherwise be dynamically generated based on the value + * of the "log-level" property. + * + * @return + * An InputStream that streams the contents of the "logback.xml" file + * that Logback should read to configure logging to Guacamole. + */ + private InputStream getLogbackConfiguration() { // Check for custom logback.xml - File logbackConfiguration = new File(guacamoleHome, "logback.xml"); - if (!logbackConfiguration.exists()) - return; + File logbackFile = new File(environment.getGuacamoleHome(), "logback.xml"); + if (logbackFile.exists()) { + try { + logger.info("Loading logback configuration from \"{}\".", logbackFile); + return new FileInputStream(logbackFile); + } + catch (FileNotFoundException e) { + logger.info("Logback configuration could not be read " + + "from \"{}\": {}", logbackFile, e.getMessage()); + } + } - logger.info("Loading logback configuration from \"{}\".", logbackConfiguration); + // Default to generating an internal logback.xml based on a simple + // "log-level" property + LogLevel level; + try { + level = environment.getProperty(LOG_LEVEL, LogLevel.INFO); + logger.info("Logging will be at the \"{}\" level.", level.getCanonicalName()); + } + catch (GuacamoleException e) { + level = LogLevel.INFO; + logger.error("Falling back to \"{}\" log level: {}", level.getCanonicalName(), e.getMessage()); + } - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - context.reset(); + return level.getLogbackConfiguration(); - try { + } + + @Override + protected void configure() { + + try (InputStream logbackConfiguration = getLogbackConfiguration()) { + + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + context.reset(); // Initialize logback JoranConfigurator configurator = new JoranConfigurator(); @@ -86,7 +136,11 @@ protected void configure() { } catch (JoranException e) { logger.error("Initialization of logback failed: {}", e.getMessage()); - logger.debug("Unable to load logback configuration..", e); + logger.debug("Unable to load logback configuration.", e); + } + catch (IOException e) { + logger.warn("Logback configuration file could not be cleanly closed: {}", e.getMessage()); + logger.debug("Failed to close logback configuration file.", e); } } diff --git a/guacamole/src/main/resources/logback.xml b/guacamole/src/main/resources/logback.xml deleted file mode 100644 index 0b91a429f2..0000000000 --- a/guacamole/src/main/resources/logback.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - \ No newline at end of file From 1b271a2407279c56aa864c9de5b1c6490e032a36 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 23 Apr 2024 10:19:05 -0700 Subject: [PATCH 6/6] GUACAMOLE-374: Enable automatic IP banning (brute-force throttling) by default. --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8a3226acbc..f85e040d43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -97,8 +97,9 @@ RUN useradd --system --create-home --shell /usr/sbin/nologin --uid $UID --gid $G USER guacamole # Environment variable defaults -ENV GUACAMOLE_HOME=/etc/guacamole \ - ENABLE_FILE_ENVIRONMENT_PROPERTIES=true +ENV BAN_ENABLED=true \ + ENABLE_FILE_ENVIRONMENT_PROPERTIES=true \ + GUACAMOLE_HOME=/etc/guacamole # Start Guacamole under Tomcat, listening on 0.0.0.0:8080 EXPOSE 8080