GUACAMOLE-374: Merge generalize Docker image to automatically map environment variables to properties.

This commit is contained in:
Virtually Nick
2024-04-25 20:44:03 -04:00
committed by GitHub
21 changed files with 1246 additions and 1478 deletions

View File

@@ -61,6 +61,9 @@ ENV \
# Add configuration scripts # Add configuration scripts
COPY guacamole-docker/bin/ /opt/guacamole/bin/ 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 source to container for sake of build
COPY . "$BUILD_DIR" COPY . "$BUILD_DIR"
@@ -68,12 +71,14 @@ COPY . "$BUILD_DIR"
# Run the build itself # Run the build itself
RUN /opt/guacamole/bin/build-guacamole.sh "$BUILD_DIR" /opt/guacamole 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 # For the runtime image, we start with the official Tomcat distribution
FROM tomcat:${TOMCAT_VERSION}-${TOMCAT_JRE} 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 \ RUN apt-get update -qq \
&& apt-get install -y xmlstarlet unzip\ && apt-get install -y xmlstarlet \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# This is where the build artifacts go in the runtime image # This is where the build artifacts go in the runtime image
@@ -91,6 +96,11 @@ RUN useradd --system --create-home --shell /usr/sbin/nologin --uid $UID --gid $G
# Run with user guacamole # Run with user guacamole
USER guacamole USER guacamole
# Environment variable defaults
ENV BAN_ENABLED=true \
ENABLE_FILE_ENVIRONMENT_PROPERTIES=true \
GUACAMOLE_HOME=/etc/guacamole
# Start Guacamole under Tomcat, listening on 0.0.0.0:8080 # Start Guacamole under Tomcat, listening on 0.0.0.0:8080
EXPOSE 8080 EXPOSE 8080
CMD ["/opt/guacamole/bin/start.sh" ] CMD ["/opt/guacamole/bin/entrypoint.sh" ]

View File

@@ -1,4 +1,4 @@
#!/bin/sh -e #!/bin/bash -e
# #
# Licensed to the Apache Software Foundation (ASF) under one # Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file # or more contributor license agreements. See the NOTICE file
@@ -23,10 +23,15 @@
## ##
## Builds Guacamole, saving "guacamole.war" and all applicable extension .jars ## Builds Guacamole, saving "guacamole.war" and all applicable extension .jars
## using the guacamole-client source contained within the given directory. ## using the guacamole-client source contained within the given directory.
## Extension files will be grouped by their associated type, with all MySQL ## Extension files will be grouped by their associated type, identical to
## files being placed within the "mysql/" subdirectory of the destination, all ## extracting the .tar.gz files included with each Guacamole release except
## PostgreSQL files being placed within the "postgresql/" subdirectory of the ## that version numbers are stripped from directory and .jar file names.
## destination, etc. ##
## 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 ## @param BUILD_DIR
## The directory which currently contains the guacamole-client source and ## The directory which currently contains the guacamole-client source and
@@ -39,164 +44,21 @@
## extension type. ## extension type.
## ##
##
## The directory which currently contains the guacamole-client source and in
## which the build should be performed.
##
BUILD_DIR="$1" 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" DESTINATION="$2"
# # Run all scripts within the "build.d" directory
# Create destination, if it does not yet exist 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
#
# 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-build-and-install-guacamole.sh
##
## Builds the Guacamole web application and all main extensions, installing the
## resulting binaries to standard locations within the Docker image. After the
## build and install process, the resulting binaries can be found beneath:
##
## /opt/guacamole/webapp:
## The web application, "guacamole.war".
##
## /opt/guacamole/extensions:
## All extensions, each within their own subdirectory and identical to the
## result of extracting a released .tar.gz except that version numbers of been
## stripped.
##
#
# Build guacamole.war and all extensions, applying any provided Maven build
# arguments
#
cd "$BUILD_DIR"
mvn $MAVEN_ARGUMENTS package
#
# Copy built web application (guacamole.war) to destination location
#
mkdir -p "$DESTINATION/webapp"
cp guacamole/target/*.war "$DESTINATION/webapp/guacamole.war"
#
# Extract all extensions to destination location, stripping version number
# suffix from .jar files and top-level directory name
#
mkdir -p "$DESTINATION/extensions"
find extensions/ -path "**/target/*.tar.gz" -exec tar -xzf "{}" \
-C "$DESTINATION/extensions" \
--xform='s#^\([^/]*\)-[0-9]\+\.[0-9]\+\.[0-9]\+#\1#g' \
--xform='s#-[0-9]\+\.[0-9]\+\.[0-9]\+\(\.jar$\)#\1#g' \
";"

View File

@@ -0,0 +1,118 @@
#
# 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 020-map-guacamole-extensions.sh
##
## Maps all installed Guacamole extensions (built in a previous step) to their
## corresponding environment variable prefixes, adding symbolic links so that
## the changes to the contents of GUACAMOLE_HOME can be easily made by the
## container's entrypoint based on which environment variables are set, without
## requiring that the entrypoint be specifically aware of all supported
## environment variables.
##
##
## Reads a mapping of Guacamole extension to environment variable prefix from
## STDIN, creating a hierarchy of directories and symbolic links on the
## filesystem that can be easily consumed by the container's entrypoint later.
##
## Each mapping consists of a single line with two values separated by
## whitespace, where the first (leftmost) value is the path to the directory
## containing the extension .jar file (relative to /opt/guacamole/extensions)
## and the second (rightmost) value is the environment variable prefix used by
## that extension. For readability, periods may be used in lieu of spaces.
##
## After mapping has occurred, the resulting mappings are located beneath
## /opt/guacamole/environment. They consist of directories named after the
## provided environment variable prefixes, where the contents of those
## directories are subsets of the contents of GUACAMOLE_HOME that would need to
## be added to the actual GUACAMOLE_HOME to enable that extension.
##
map_extensions() {
# Read through each provided path/prefix mapping pair
mkdir -p "$DESTINATION/environment"
tr . ' ' | while read -r EXT_PATH VAR_PREFIX; do
# Add mappings only for extensions that were actually built as part of
# the build process (some extensions, like the RADIUS support, will
# only be built if specific build arguments are provided)
if [ -d "$DESTINATION/extensions/$EXT_PATH/" ]; then
echo "Mapped: $EXT_PATH -> $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

View File

@@ -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"

View File

@@ -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

View File

@@ -0,0 +1,116 @@
#
# 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_"
# 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
# The old "LOGBACK_LEVEL" environment variable has been replaced with
# "LOG_LEVEL" for consistency with the guacd image
deprecate_variable "LOGBACK_LEVEL" "LOG_LEVEL"

View File

@@ -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" <<EOF
# guacamole.properties - generated `date`
EOF
#
# Copy contents of provided GUACAMOLE_HOME template, if any
#
if [ -e "$GUACAMOLE_HOME_TEMPLATE" ]; then
# Create links for any libraries provided in the template GUACAMOLE_HOME
find "$GUACAMOLE_HOME_TEMPLATE/lib" -mindepth 1 -maxdepth 1 \
-exec ln -sv "{}" "$GUACAMOLE_HOME/lib/" ";"
# Create links for any extensions provided in the template GUACAMOLE_HOME
find "$GUACAMOLE_HOME_TEMPLATE/extensions" -mindepth 1 -maxdepth 1 \
-exec ln -sv "{}" "$GUACAMOLE_HOME/extensions/" ";"
# Create links for all other files directly within the template
# GUACAMOLE_HOME
find "$GUACAMOLE_HOME_TEMPLATE" -mindepth 1 -maxdepth 1 \
-name guacamole.properties -o -name lib -o -name extensions -prune \
-o -exec ln -sv "{}" "$GUACAMOLE_HOME/" ";"
# Add any properties provided within template GUACAMOLE_HOME
if [ -e "$GUACAMOLE_HOME_TEMPLATE/guacamole.properties" ]; then
cat "$GUACAMOLE_HOME_TEMPLATE/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

View File

@@ -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" <<EOF
# Point Guacamole at automatically-generated, temporary GUACAMOLE_HOME
guacamole.home=$GUACAMOLE_HOME
EOF
# Install webapp
ln -sf /opt/guacamole/webapp/guacamole.war $CATALINA_BASE/webapps/${WEBAPP_CONTEXT:-guacamole}.war

View File

@@ -0,0 +1,88 @@
#
# 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 800-configure-features.sh
##
## Automatically checks all environment variables currently set and performs
## configuration tasks related to those variabels, including installing any
## extensions and external libraries associated with those variables to
## GUACAMOLE_HOME. Only environment variable prefixes are considered; this
## script is not aware of whether an extension actually uses an environment
## variable.
##
##
## Returns whether the feature associated with a particular environment
## variable prefix has configuration values set. Only the presence of
## environment variables having that prefix is checked. Features can also be
## entirely enabled/disabled through setting the [PREFIX_]ENABLED variable to
## true/false respectively, where "[PREFIX_]" is the specified environment
## variable prefix (including trailing underscore).
##
## @param VAR_BASE
## The environment variable prefix to check, including trailing underscore.
##
## @returns
## Zero if the feature associated with the given environment variable
## prefix is enabled, non-zero otherwise.
##
is_feature_enabled() {
local VAR_BASE="$1"
# Allow any feature to be explicitly enabled/disabled using a
# [PREFIX_]ENABLED variable
local ENABLED_VAR="${VAR_BASE}ENABLED"
if [ "${!ENABLED_VAR}" = "true" ]; then
return 0
elif [ "${!ENABLED_VAR}" = "false" ]; then
return 1
fi
# Lacking an explicit request to enable/disable the feature, rely on
# implicit enable/disable via presence of any other variables having the
# given prefix
awk 'BEGIN{for(v in ENVIRON) print v}' | grep "^${VAR_BASE}" > /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

View File

@@ -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

View File

@@ -0,0 +1,61 @@
#
# 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".
##
##
## 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 )
# Translate all properties supported by RemoteIpValve into corresponding
# environment variables
for ATTRIBUTE in \
remoteIpHeader \
internalProxies \
proxiesHeader \
trustedProxies \
protocolHeader \
protocolHeaderHttpsValue \
httpServerPort \
httpsServerPort; do
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
done
# 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"

View File

@@ -20,6 +20,7 @@
package org.apache.guacamole.properties; package org.apache.guacamole.properties;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@@ -45,8 +46,10 @@ public abstract class EnumGuacamoleProperty<T extends Enum<T>> implements Guacam
/** /**
* Defines the string value which should be accepted and parsed into the * 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) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
public static @interface PropertyValue { public static @interface PropertyValue {
@@ -63,6 +66,29 @@ public abstract class EnumGuacamoleProperty<T extends Enum<T>> 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 * Mapping of valid property values to the corresponding enum constants
* that those values parse to. * that those values parse to.
@@ -103,7 +129,17 @@ public abstract class EnumGuacamoleProperty<T extends Enum<T>> implements Guacam
+ "match declared values.", e); + "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); PropertyValue valueAnnotation = field.getAnnotation(PropertyValue.class);
if (valueAnnotation != null) if (valueAnnotation != null)
valueMapping.put(valueAnnotation.value(), value); valueMapping.put(valueAnnotation.value(), value);

View File

@@ -70,6 +70,7 @@ public class EnumGuacamolePropertyTest {
* @see <a href="https://en.wikipedia.org/wiki/Tuna">Tuna (Wikipedia)</a> * @see <a href="https://en.wikipedia.org/wiki/Tuna">Tuna (Wikipedia)</a>
*/ */
@PropertyValue("tuna") @PropertyValue("tuna")
@PropertyValue("yellowfin")
TUNA, TUNA,
/** /**
@@ -135,6 +136,7 @@ public class EnumGuacamolePropertyTest {
assertEquals(Fish.TROUT, FAVORITE_FISH.parseValue("trout")); assertEquals(Fish.TROUT, FAVORITE_FISH.parseValue("trout"));
assertEquals(Fish.MACKEREL, FAVORITE_FISH.parseValue("mackerel")); assertEquals(Fish.MACKEREL, FAVORITE_FISH.parseValue("mackerel"));
assertEquals(Fish.TUNA, FAVORITE_FISH.parseValue("tuna")); assertEquals(Fish.TUNA, FAVORITE_FISH.parseValue("tuna"));
assertEquals(Fish.TUNA, FAVORITE_FISH.parseValue("yellowfin"));
assertEquals(Fish.SARDINE, FAVORITE_FISH.parseValue("sardine")); assertEquals(Fish.SARDINE, FAVORITE_FISH.parseValue("sardine"));
} }
@@ -164,7 +166,7 @@ public class EnumGuacamolePropertyTest {
} }
catch (GuacamoleException e) { catch (GuacamoleException e) {
String message = e.getMessage(); String message = e.getMessage();
assertTrue(message.contains("\"mackerel\", \"salmon\", \"sardine\", \"trout\", \"tuna\"")); assertTrue(message.contains("\"mackerel\", \"salmon\", \"sardine\", \"trout\", \"tuna\", \"yellowfin\""));
} }
} }

View File

@@ -95,7 +95,7 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
/** /**
* A property that determines whether environment variables are evaluated * 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 = private static final BooleanGuacamoleProperty ENABLE_ENVIRONMENT_PROPERTIES =
new BooleanGuacamoleProperty() { new BooleanGuacamoleProperty() {
@@ -105,6 +105,19 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
} }
}; };
/**
* 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. * The Guacamole server environment.
*/ */
@@ -172,6 +185,23 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
logger.debug("Error reading \"{}\" property from guacamole.properties.", ENABLE_ENVIRONMENT_PROPERTIES.getName(), e); 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 // Now that at least the main guacamole.properties source of
// configuration information is available, initialize the session map // configuration information is available, initialize the session map
sessionMap = new HashTokenSessionMap(environment); sessionMap = new HashTokenSessionMap(environment);
@@ -210,9 +240,20 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
return current; return current;
// Create new injector if necessary // Create new injector if necessary
Injector injector = Guice.createInjector(Stage.PRODUCTION, Injector injector =
// Ensure environment and logging are configured FIRST ...
Guice.createInjector(Stage.PRODUCTION,
new EnvironmentModule(environment), new EnvironmentModule(environment),
new LogModule(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 ExtensionModule(environment),
new RESTServiceModule(sessionMap), new RESTServiceModule(sessionMap),
new TunnelModule() new TunnelModule()

View File

@@ -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;
}
}

View File

@@ -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 =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<configuration>\n"
+ "\n"
+ " <!-- Default appender -->\n"
+ " <appender name=\"GUAC-DEFAULT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n"
+ " <encoder>\n"
+ " <pattern>%%d{HH:mm:ss.SSS} [%%thread] %%-5level %%logger{36} - %%msg%%n</pattern>\n"
+ " </encoder>\n"
+ " </appender>\n"
+ "\n"
+ " <!-- Log at level defined with \"log-level\" property -->\n"
+ " <root level=\"%s\">\n"
+ " <appender-ref ref=\"GUAC-DEFAULT\" />\n"
+ " </root>\n"
+ "\n"
+ "</configuration>\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);
}
}

View File

@@ -25,7 +25,13 @@ import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter; import ch.qos.logback.core.util.StatusPrinter;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import java.io.File; 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.environment.Environment;
import org.apache.guacamole.properties.EnumGuacamoleProperty;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -44,6 +50,19 @@ public class LogModule extends AbstractModule {
*/ */
private final Environment environment; 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<LogLevel> LOG_LEVEL = new EnumGuacamoleProperty<LogLevel>(LogLevel.class) {
@Override
public String getName() {
return "log-level";
}
};
/** /**
* Creates a new LogModule which uses the given environment to determine * Creates a new LogModule which uses the given environment to determine
* the logging configuration. * the logging configuration.
@@ -55,26 +74,57 @@ public class LogModule extends AbstractModule {
this.environment = environment; this.environment = environment;
} }
/**
* 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 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());
}
}
// 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());
}
return level.getLogbackConfiguration();
}
@Override @Override
protected void configure() { protected void configure() {
// Only load logback configuration if GUACAMOLE_HOME exists try (InputStream logbackConfiguration = getLogbackConfiguration()) {
File guacamoleHome = environment.getGuacamoleHome();
if (!guacamoleHome.isDirectory())
return;
// Check for custom logback.xml
File logbackConfiguration = new File(guacamoleHome, "logback.xml");
if (!logbackConfiguration.exists())
return;
logger.info("Loading logback configuration from \"{}\".", logbackConfiguration);
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.reset(); context.reset();
try {
// Initialize logback // Initialize logback
JoranConfigurator configurator = new JoranConfigurator(); JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context); configurator.setContext(context);
@@ -86,7 +136,11 @@ public class LogModule extends AbstractModule {
} }
catch (JoranException e) { catch (JoranException e) {
logger.error("Initialization of logback failed: {}", e.getMessage()); 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);
} }
} }

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<configuration>
<!-- Default appender -->
<appender name="GUAC-DEFAULT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Log at INFO level -->
<root level="info">
<appender-ref ref="GUAC-DEFAULT" />
</root>
</configuration>