GUACAMOLE-1289: Merge refactor Duo and authentication flow
diff --git a/Dockerfile b/Dockerfile
index 29231f3..f85e040 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -61,6 +61,9 @@
# 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,12 +71,14 @@
# 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}
-# 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
@@ -91,6 +96,11 @@
# Run with 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
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 2fc6c95..595bd70 100755
--- a/guacamole-docker/bin/build-guacamole.sh
+++ b/guacamole-docker/bin/build-guacamole.sh
@@ -1,4 +1,4 @@
-#!/bin/sh -e
+#!/bin/bash -e
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -23,10 +23,15 @@
##
## Builds Guacamole, saving "guacamole.war" and all applicable extension .jars
## using the guacamole-client source contained within the given directory.
-## Extension files will be grouped by their associated type, with all MySQL
-## files being placed within the "mysql/" subdirectory of the destination, all
-## PostgreSQL files being placed within the "postgresql/" subdirectory of the
-## destination, etc.
+## Extension files will be grouped by their associated type, identical to
+## extracting the .tar.gz files included with each Guacamole release except
+## that version numbers are stripped from directory and .jar file names.
+##
+## The build process is split across multiple scripts within the
+## /opt/guacamole/build.d directory. Additional steps may be added to the
+## build process by adding .sh scripts to this directory. Any such scripts MUST
+## be shell scripts ending with a ".sh" extension and MUST be written for bash
+## (the shell used by this entrypoint).
##
## @param BUILD_DIR
## The directory which currently contains the guacamole-client source and
@@ -39,164 +44,21 @@
## extension type.
##
+##
+## The directory which currently contains the guacamole-client source and in
+## which the build should be performed.
+##
BUILD_DIR="$1"
+
+##
+## The directory to save guacamole.war within, along with all extension .jars.
+## Note that this script will create extension-specific subdirectories within
+## this directory, and files will thus be grouped by extension type.
+##
DESTINATION="$2"
-#
-# Create destination, if it does not yet exist
-#
+# Run all scripts within the "build.d" directory
+for SCRIPT in /opt/guacamole/build.d/*.sh; do
+ source "$SCRIPT"
+done
-mkdir -p "$DESTINATION"
-
-#
-# Build guacamole.war and all extensions
-#
-
-cd "$BUILD_DIR"
-
-#
-# Run the maven build, applying any arbitrary provided maven arguments.
-#
-
-mvn $MAVEN_ARGUMENTS package
-
-#
-# Copy guacamole.war to destination
-#
-
-cp guacamole/target/*.war "$DESTINATION/guacamole.war"
-
-#
-# Copy JDBC auth extensions and SQL scripts
-#
-
-tar -xzf extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-dist/target/*.tar.gz \
- -C "$DESTINATION" \
- --wildcards \
- --no-anchored \
- --strip-components=1 \
- "*.jar" \
- "*.sql"
-
-#
-# Download MySQL JDBC driver
-#
-
-echo "Downloading MySQL Connector/J ..."
-curl -L "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-j-$MYSQL_JDBC_VERSION.tar.gz" | \
-tar -xz \
- -C "$DESTINATION/mysql/" \
- --wildcards \
- --no-anchored \
- --no-wildcards-match-slash \
- --strip-components=1 \
- "mysql-connector-*.jar"
-
-#
-# Download PostgreSQL JDBC driver
-#
-
-echo "Downloading PostgreSQL JDBC driver ..."
-curl -L "https://jdbc.postgresql.org/download/postgresql-$PGSQL_JDBC_VERSION.jar" \
- > "$DESTINATION/postgresql/postgresql-$PGSQL_JDBC_VERSION.jar"
-
-#
-# Copy SSO auth extensions
-#
-
-tar -xzf extensions/guacamole-auth-sso/modules/guacamole-auth-sso-dist/target/*.tar.gz \
- -C "$DESTINATION" \
- --wildcards \
- --no-anchored \
- --strip-components=1 \
- "*.jar"
-
-#
-# Download SQL Server JDBC driver
-#
-
-echo "Downloading SQL Server JDBC driver ..."
-curl -L "https://github.com/microsoft/mssql-jdbc/releases/download/v$MSSQL_JDBC_VERSION/mssql-jdbc-$MSSQL_JDBC_VERSION.jre8.jar" \
- > "$DESTINATION/sqlserver/mssql-jdbc-$MSSQL_JDBC_VERSION.jre8.jar" \
-
-#
-# Copy LDAP auth extension and schema modifications
-#
-
-mkdir -p "$DESTINATION/ldap"
-tar -xzf extensions/guacamole-auth-ldap/target/*.tar.gz \
- -C "$DESTINATION/ldap" \
- --wildcards \
- --no-anchored \
- --xform="s#.*/##" \
- "*.jar" \
- "*.ldif"
-
-#
-# Copy Radius auth extension if it was build
-#
-
-if [ -f extensions/guacamole-auth-radius/target/guacamole-auth-radius*.jar ]; then
- mkdir -p "$DESTINATION/radius"
- cp extensions/guacamole-auth-radius/target/guacamole-auth-radius*.jar "$DESTINATION/radius"
-fi
-
-#
-# Copy TOTP auth extension if it was built
-#
-
-if [ -f extensions/guacamole-auth-totp/target/guacamole-auth-totp*.jar ]; then
- mkdir -p "$DESTINATION/totp"
- cp extensions/guacamole-auth-totp/target/guacamole-auth-totp*.jar "$DESTINATION/totp"
-fi
-
-#
-# Copy Duo auth extension if it was built
-#
-
-if [ -f extensions/guacamole-auth-duo/target/*.tar.gz ]; then
- mkdir -p "$DESTINATION/duo"
- tar -xzf extensions/guacamole-auth-duo/target/*.tar.gz \
- -C "$DESTINATION/duo/" \
- --wildcards \
- --no-anchored \
- --no-wildcards-match-slash \
- --strip-components=1 \
- "*.jar"
-fi
-
-#
-# Copy header auth extension if it was built
-#
-
-if [ -f extensions/guacamole-auth-header/target/guacamole-auth-header*.jar ]; then
- mkdir -p "$DESTINATION/header"
- cp extensions/guacamole-auth-header/target/guacamole-auth-header*.jar "$DESTINATION/header"
-fi
-
-#
-# Copy json auth extension if it was built
-#
-
-if [ -f extensions/guacamole-auth-json/target/guacamole-auth-json*.jar ]; then
- mkdir -p "$DESTINATION/json"
- cp extensions/guacamole-auth-json/target/guacamole-auth-json*.jar "$DESTINATION/json"
-fi
-
-#
-# Copy automatic brute-force banning auth extension if it was built
-#
-
-if [ -f extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar ]; then
- mkdir -p "$DESTINATION/ban"
- cp extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar "$DESTINATION/ban"
-fi
-
-#
-# Copy history recording storage extension if it was built
-#
-
-if [ -f extensions/guacamole-history-recording-storage/target/guacamole-history-recording-storage*.jar ]; then
- mkdir -p "$DESTINATION/recordings"
- cp extensions/guacamole-history-recording-storage/target/guacamole-history-recording-storage*.jar "$DESTINATION/recordings"
-fi
diff --git a/guacamole-docker/bin/entrypoint.sh b/guacamole-docker/bin/entrypoint.sh
new file mode 100755
index 0000000..509332f
--- /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 4588c0e..0000000
--- 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 <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using a MySQL database, you must provide each of the following
-environment variables or their corresponding Docker secrets by appending _FILE
-to the environment variable, and setting the value to the path of the
-corresponding secret:
-
- MYSQL_USER The user to authenticate as when connecting to
- MySQL.
-
- MYSQL_PASSWORD The password to use when authenticating with MySQL as
- MYSQL_USER.
-
- MYSQL_DATABASE The name of the MySQL database to use for Guacamole
- authentication.
-END
- exit 1;
-}
-
-
-##
-## Adds properties to guacamole.properties which select the MySQL
-## authentication provider, and configure it to connect to the linked MySQL
-## container. If a MySQL database is explicitly specified using the
-## MYSQL_HOSTNAME and MYSQL_PORT environment variables, that will be used
-## instead of a linked container.
-##
-associate_mysql() {
-
- # Use linked container if specified
- if [ -n "$MYSQL_NAME" ]; then
- MYSQL_HOSTNAME="$MYSQL_PORT_3306_TCP_ADDR"
- MYSQL_PORT="$MYSQL_PORT_3306_TCP_PORT"
- fi
-
- # Use default port if none specified
- MYSQL_PORT="${MYSQL_PORT-3306}"
-
- # Verify required connection information is present
- if [ -z "$MYSQL_HOSTNAME" -o -z "$MYSQL_PORT" ]; then
- cat <<END
-FATAL: Missing MYSQL_HOSTNAME or "mysql" link.
--------------------------------------------------------------------------------
-If using a MySQL database, you must either:
-
-(a) Explicitly link that container with the link named "mysql".
-
-(b) If not using a Docker container for MySQL, explicitly specify the TCP
- connection to your database using the following environment variables:
-
- MYSQL_HOSTNAME The hostname or IP address of the MySQL server. If not
- using a MySQL Docker container and corresponding link,
- this environment variable is *REQUIRED*.
-
- MYSQL_PORT The port on which the MySQL server is listening for TCP
- connections. This environment variable is option. If
- omitted, the standard MySQL port of 3306 will be used.
-END
- exit 1;
- fi
-
-
- # Verify that the required Docker secrets are present, else, default to their normal environment variables
- if [ -n "$MYSQL_USER_FILE" ]; then
- set_property "mysql-username" "`cat "$MYSQL_USER_FILE"`"
- elif [ -n "$MYSQL_USER" ]; then
- set_property "mysql-username" "$MYSQL_USER"
- else
- mysql_missing_vars
- exit 1;
- fi
-
- if [ -n "$MYSQL_PASSWORD_FILE" ]; then
- set_property "mysql-password" "`cat "$MYSQL_PASSWORD_FILE"`"
- elif [ -n "$MYSQL_PASSWORD" ]; then
- set_property "mysql-password" "$MYSQL_PASSWORD"
- else
- mysql_missing_vars
- exit 1;
- fi
-
- if [ -n "$MYSQL_DATABASE_FILE" ]; then
- set_property "mysql-database" "`cat "$MYSQL_DATABASE_FILE"`"
- elif [ -n "$MYSQL_DATABASE" ]; then
- set_property "mysql-database" "$MYSQL_DATABASE"
- else
- mysql_missing_vars
- exit 1;
- fi
-
- # Update config file
- set_property "mysql-hostname" "$MYSQL_HOSTNAME"
- set_property "mysql-port" "$MYSQL_PORT"
-
- set_optional_property \
- "mysql-absolute-max-connections" \
- "$MYSQL_ABSOLUTE_MAX_CONNECTIONS"
-
- set_optional_property \
- "mysql-default-max-connections" \
- "$MYSQL_DEFAULT_MAX_CONNECTIONS"
-
- set_optional_property \
- "mysql-default-max-group-connections" \
- "$MYSQL_DEFAULT_MAX_GROUP_CONNECTIONS"
-
- set_optional_property \
- "mysql-default-max-connections-per-user" \
- "$MYSQL_DEFAULT_MAX_CONNECTIONS_PER_USER"
-
- set_optional_property \
- "mysql-default-max-group-connections-per-user" \
- "$MYSQL_DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER"
-
- set_optional_property \
- "mysql-user-required" \
- "$MYSQL_USER_REQUIRED"
-
- set_optional_property \
- "mysql-ssl-mode" \
- "$MYSQL_SSL_MODE"
-
- set_optional_property \
- "mysql-ssl-trust-store" \
- "$MYSQL_SSL_TRUST_STORE"
-
- # For SSL trust store password, check secrets, first, then standard env variable
- if [ -n "$MYSQL_SSL_TRUST_PASSWORD_FILE" ]; then
- set_property "mysql-ssl-trust-password" "`cat "$MYSQL_SSL_TRUST_PASSWORD_FILE"`"
- elif [ -n "$MYSQL_SSL_TRUST_PASSWORD" ]; then
- set_property "mysql-ssl-trust-password" "$MYSQL_SSL_TRUST_PASSWORD"
- fi
-
- set_optional_property \
- "mysql-ssl-client-store" \
- "$MYSQL_SSL_CLIENT_STORE"
-
- # For SSL trust store password, check secrets, first, then standard env variable
- if [ -n "$MYSQL_SSL_CLIENT_PASSWORD_FILE" ]; then
- set_property "mysql-ssl-client-password" "`cat "$MYSQL_SSL_CLIENT_PASSWORD_FILE"`"
- elif [ -n "$MYSQL_SSL_CLIENT_PASSWORD" ]; then
- set_property "mysql-ssl-client-password" "$MYSQL_SSL_CLIENT_PASSWORD"
- fi
-
- set_optional_property \
- "mysql-auto-create-accounts" \
- "$MYSQL_AUTO_CREATE_ACCOUNTS"
-
- # Add required .jar files to GUACAMOLE_LIB and GUACAMOLE_EXT
- ln -s /opt/guacamole/mysql/mysql-connector-*.jar "$GUACAMOLE_LIB"
- ln -s /opt/guacamole/mysql/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-
-}
-
-# Print error message regarding missing required variables for PostgreSQL authentication
-postgresql_missing_vars() {
- cat <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using a PostgreSQL database, you must provide each of the following
-environment variables or their corresponding Docker secrets by appending _FILE
-to the environment variable, and setting the value to the path of the
-corresponding secret:
-
- POSTGRESQL_USER The user to authenticate as when connecting to
- PostgreSQL.
-
- POSTGRESQL_PASSWORD The password to use when authenticating with PostgreSQL
- as POSTGRESQL_USER.
-
- POSTGRESQL_DATABASE The name of the PostgreSQL database to use for Guacamole
- authentication.
-END
- exit 1;
-}
-
-## Provide backward compatibility on POSTGRES_* environment variables
-## In case of new deployment, please use POSTGRESQL_* equivalent variables.
-for VAR_BASE in \
- HOSTNAME PORT \
- DATABASE USER PASSWORD \
- DATABASE_FILE USER_FILE PASSWORD_FILE \
- ABSOLUTE_MAX_CONNECTIONS DEFAULT_MAX_CONNECTIONS \
- DEFAULT_MAX_GROUP_CONNECTIONS DEFAULT_MAX_CONNECTIONS_PER_USER \
- DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER \
- DEFAULT_STATEMENT_TIMEOUT SOCKET_TIMEOUT \
- USER_REQUIRED \
- SSL_KEY_PASSWORD_FILE SSL_KEY_PASSWORD; do
-
- OLD_VAR="POSTGRES_$VAR_BASE"
- NEW_VAR="POSTGRESQL_$VAR_BASE"
-
- if [ -n "${!OLD_VAR}" ]; then
- printf -v "$NEW_VAR" "%s" "${!OLD_VAR}"
- echo "WARNING: ${OLD_VAR} detected, please use ${NEW_VAR} for further deployments."
- fi
-
-done
-
-##
-## Adds properties to guacamole.properties which select the PostgreSQL
-## authentication provider, and configure it to connect to the linked
-## PostgreSQL container. If a PostgreSQL database is explicitly specified using
-## the POSTGRESQL_HOSTNAME and POSTGRESQL_PORT environment variables, that will be
-## used instead of a linked container.
-##
-associate_postgresql() {
-
- # Use linked container if specified
- if [ -n "$POSTGRES_NAME" ]; then
- POSTGRESQL_HOSTNAME="$POSTGRES_PORT_5432_TCP_ADDR"
- POSTGRESQL_PORT="$POSTGRES_PORT_5432_TCP_PORT"
- fi
-
- # Use default port if none specified
- POSTGRESQL_PORT="${POSTGRESQL_PORT-5432}"
-
- # Verify required connection information is present
- if [ -z "$POSTGRESQL_HOSTNAME" -o -z "$POSTGRESQL_PORT" ]; then
- cat <<END
-FATAL: Missing POSTGRESQL_HOSTNAME or "postgres" link.
--------------------------------------------------------------------------------
-If using a PostgreSQL database, you must either:
-
-(a) Explicitly link that container with the link named "postgres".
-
-(b) If not using a Docker container for PostgreSQL, explicitly specify the TCP
- connection to your database using the following environment variables:
-
- POSTGRESQL_HOSTNAME The hostname or IP address of the PostgreSQL server. If
- not using a PostgreSQL Docker container and
- corresponding link, this environment variable is
- *REQUIRED*.
-
- POSTGRESQL_PORT The port on which the PostgreSQL server is listening for
- TCP connections. This environment variable is option. If
- omitted, the standard PostgreSQL port of 5432 will be
- used.
-END
- exit 1;
- fi
-
- # Verify that the required Docker secrets are present, else, default to their normal environment variables
- if [ -n "$POSTGRESQL_USER_FILE" ]; then
- set_property "postgresql-username" "`cat "$POSTGRESQL_USER_FILE"`"
- elif [ -n "$POSTGRESQL_USER" ]; then
- set_property "postgresql-username" "$POSTGRESQL_USER"
- else
- postgresql_missing_vars
- exit 1;
- fi
-
- if [ -n "$POSTGRESQL_PASSWORD_FILE" ]; then
- set_property "postgresql-password" "`cat "$POSTGRESQL_PASSWORD_FILE"`"
- elif [ -n "$POSTGRESQL_PASSWORD" ]; then
- set_property "postgresql-password" "$POSTGRESQL_PASSWORD"
- else
- postgresql_missing_vars
- exit 1;
- fi
-
- if [ -n "$POSTGRESQL_DATABASE_FILE" ]; then
- set_property "postgresql-database" "`cat "$POSTGRESQL_DATABASE_FILE"`"
- elif [ -n "$POSTGRESQL_DATABASE" ]; then
- set_property "postgresql-database" "$POSTGRESQL_DATABASE"
- else
- postgresql_missing_vars
- exit 1;
- fi
-
- # Update config file
- set_property "postgresql-hostname" "$POSTGRESQL_HOSTNAME"
- set_property "postgresql-port" "$POSTGRESQL_PORT"
-
- set_optional_property \
- "postgresql-absolute-max-connections" \
- "$POSTGRESQL_ABSOLUTE_MAX_CONNECTIONS"
-
- set_optional_property \
- "postgresql-default-max-connections" \
- "$POSTGRESQL_DEFAULT_MAX_CONNECTIONS"
-
- set_optional_property \
- "postgresql-default-max-group-connections" \
- "$POSTGRESQL_DEFAULT_MAX_GROUP_CONNECTIONS"
-
- set_optional_property \
- "postgresql-default-max-connections-per-user" \
- "$POSTGRESQL_DEFAULT_MAX_CONNECTIONS_PER_USER"
-
- set_optional_property \
- "postgresql-default-max-group-connections-per-user" \
- "$POSTGRESQL_DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER"
-
- set_optional_property \
- "postgresql-default-statement-timeout" \
- "$POSTGRESQL_DEFAULT_STATEMENT_TIMEOUT"
-
- set_optional_property \
- "postgresql-user-required" \
- "$POSTGRESQL_USER_REQUIRED"
-
- set_optional_property \
- "postgresql-socket-timeout" \
- "$POSTGRESQL_SOCKET_TIMEOUT"
-
- set_optional_property \
- "postgresql-ssl-mode" \
- "$POSTGRESQL_SSL_MODE"
-
- set_optional_property \
- "postgresql-ssl-cert-file" \
- "$POSTGRESQL_SSL_CERT_FILE"
-
- set_optional_property \
- "postgresql-ssl-key-file" \
- "$POSTGRESQL_SSL_KEY_FILE"
-
- set_optional_property \
- "postgresql-ssl-root-cert-file" \
- "$POSTGRESQL_SSL_ROOT_CERT_FILE"
-
- # For SSL key password, check secrets, first, then standard env variable
- if [ -n "$POSTGRESQL_SSL_KEY_PASSWORD_FILE" ]; then
- set_property "postgresql-ssl-key-password" "`cat "$POSTGRESQL_SSL_KEY_PASSWORD_FILE"`"
- elif [ -n "$POSTGRESQL_SSL_KEY_PASSWORD" ]; then
- set_property "postgresql-ssl-key-password" "$POSTGRESQL_SSL_KEY_PASSWORD"
- fi
-
- set_optional_property \
- "postgresql-auto-create-accounts" \
- "$POSTGRESQL_AUTO_CREATE_ACCOUNTS"
-
- # Add required .jar files to GUACAMOLE_LIB and GUACAMOLE_EXT
- ln -s /opt/guacamole/postgresql/postgresql-*.jar "$GUACAMOLE_LIB"
- ln -s /opt/guacamole/postgresql/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-
-}
-
-# Print error message regarding missing required variables for SQLServer authentication
-sqlserver_missing_vars() {
- cat <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using a SQLServer database, you must provide each of the following
-environment variables:
-
- SQLSERVER_USER The user to authenticate as when connecting to
- SQLServer.
-
- SQLSERVER_PASSWORD The password to use when authenticating with SQLServer
- as SQLSERVER_USER.
-
- SQLSERVER_DATABASE The name of the SQLServer database to use for Guacamole
- authentication.
-
-Alternatively, if you want to store database credentials using Docker secrets,
-set the path of the corresponding secrets in the following three variables:
-
- SQLSERVER_DATABASE_FILE The path of the docker secret containing the name
- of database to use for Guacamole authentication.
-
- SQLSERVER_USER_FILE The path of the docker secret containing the name of
- the user that Guacamole will use to connect to SQLServer.
-
- SQLSERVER_PASSWORD_FILE The path of the docker secret containing the
- password that Guacamole will provide when connecting to
- SQLServer as SQLSERVER_USER.
-
-END
- exit 1;
-}
-
-##
-## Adds properties to guacamole.properties which select the SQLServer
-## authentication provider, and configure it to connect to the linked
-## SQLServer container. If a SQLServer database is explicitly specified using
-## the SQLSERVER_HOSTNAME and SQLSERVER_PORT environment variables, that will
-## be used instead of a linked container.
-##
-associate_sqlserver() {
-
- # Use linked container if specified
- if [ -n "$SQLSERVER_NAME" ]; then
- SQLSERVER_HOSTNAME="$SQLSERVER_PORT_1433_TCP_ADDR"
- SQLSERVER_PORT="$SQLSERVER_PORT_1433_TCP_PORT"
- fi
-
- # Use default port if none specified
- SQLSERVER_PORT="${SQLSERVER_PORT-1433}"
-
- # Verify required connection information is present
- if [ -z "$SQLSERVER_HOSTNAME" -o -z "$SQLSERVER_PORT" ]; then
- cat <<END
-FATAL: Missing SQLSERVER_HOSTNAME or "sqlserver" link.
--------------------------------------------------------------------------------
-If using a SQLServer database, you must either:
-
-(a) Explicitly link that container with the link named "sqlserver".
-
-(b) If not using a Docker container for SQLServer, explicitly specify the TCP
- connection to your database using the following environment variables:
-
- SQLSERVER_HOSTNAME The hostname or IP address of the SQLServer server. If
- not using a SQLServer Docker container and
- corresponding link, this environment variable is
- *REQUIRED*.
-
- SQLSERVER_PORT The port on which the SQLServer server is listening for
- TCP connections. This environment variable is option. If
- omitted, the standard SQLServer port of 1433 will be
- used.
-END
- exit 1;
- fi
-
- # Verify that the required Docker secrets are present, else, default to their normal environment variables
- if [ -n "$SQLSERVER_USER_FILE" ]; then
- set_property "sqlserver-username" "`cat "$SQLSERVER_USER_FILE"`"
- elif [ -n "$SQLSERVER_USER" ]; then
- set_property "sqlserver-username" "$SQLSERVER_USER"
- else
- sqlserver_missing_vars
- exit 1;
- fi
-
- if [ -n "$SQLSERVER_PASSWORD_FILE" ]; then
- set_property "sqlserver-password" "`cat "$SQLSERVER_PASSWORD_FILE"`"
- elif [ -n "$SQLSERVER_PASSWORD" ]; then
- set_property "sqlserver-password" "$SQLSERVER_PASSWORD"
- else
- sqlserver_missing_vars
- exit 1;
- fi
-
- if [ -n "$SQLSERVER_DATABASE_FILE" ]; then
- set_property "sqlserver-database" "`cat "$SQLSERVER_DATABASE_FILE"`"
- elif [ -n "$SQLSERVER_DATABASE" ]; then
- set_property "sqlserver-database" "$SQLSERVER_DATABASE"
- else
- sqlserver_missing_vars
- exit 1;
- fi
-
- # Update config file
- set_property "sqlserver-hostname" "$SQLSERVER_HOSTNAME"
- set_property "sqlserver-port" "$SQLSERVER_PORT"
- set_property "sqlserver-driver" "microsoft2005"
-
- set_optional_property \
- "sqlserver-absolute-max-connections" \
- "$SQLSERVER_ABSOLUTE_MAX_CONNECTIONS"
-
- set_optional_property \
- "sqlserver-default-max-connections" \
- "$SQLSERVER_DEFAULT_MAX_CONNECTIONS"
-
- set_optional_property \
- "sqlserver-default-max-group-connections" \
- "$SQLSERVER_DEFAULT_MAX_GROUP_CONNECTIONS"
-
- set_optional_property \
- "sqlserver-default-max-connections-per-user" \
- "$SQLSERVER_DEFAULT_MAX_CONNECTIONS_PER_USER"
-
- set_optional_property \
- "sqlserver-default-max-group-connections-per-user" \
- "$SQLSERVER_DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER"
-
- set_optional_property \
- "sqlserver-user-required" \
- "$SQLSERVER_USER_REQUIRED"
-
- set_optional_property \
- "sqlserver-auto-create-accounts" \
- "$SQLSERVER_AUTO_CREATE_ACCOUNTS"
-
- set_optional_property \
- "sqlserver-instance" \
- "$SQLSERVER_INSTANCE"
-
- # Add required .jar files to GUACAMOLE_LIB and GUACAMOLE_EXT
- ln -s /opt/guacamole/sqlserver/mssql-jdbc-*.jar "$GUACAMOLE_LIB"
- ln -s /opt/guacamole/sqlserver/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-
-}
-
-##
-## Adds properties to guacamole.properties which select the LDAP
-## authentication provider, and configure it to connect to the specified LDAP
-## directory.
-##
-associate_ldap() {
-
- # Verify required parameters are present
- if [ -z "$LDAP_HOSTNAME" -o -z "$LDAP_USER_BASE_DN" ]; then
- cat <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using an LDAP directory, you must provide each of the following environment
-variables:
-
- LDAP_HOSTNAME The hostname or IP address of your LDAP server.
-
- LDAP_USER_BASE_DN The base DN under which all Guacamole users will be
- located. Absolutely all Guacamole users that will
- authenticate via LDAP must exist within the subtree of
- this DN.
-END
- exit 1;
- fi
-
- # Update config file
- set_property "ldap-hostname" "$LDAP_HOSTNAME"
- set_property "ldap-user-base-dn" "$LDAP_USER_BASE_DN"
-
- set_optional_property "ldap-port" "$LDAP_PORT"
- set_optional_property "ldap-encryption-method" "$LDAP_ENCRYPTION_METHOD"
- set_optional_property "ldap-max-search-results" "$LDAP_MAX_SEARCH_RESULTS"
- set_optional_property "ldap-search-bind-dn" "$LDAP_SEARCH_BIND_DN"
- set_optional_property "ldap-user-attributes" "$LDAP_USER_ATTRIBUTES"
- set_optional_property "ldap-search-bind-password" "$LDAP_SEARCH_BIND_PASSWORD"
- set_optional_property "ldap-username-attribute" "$LDAP_USERNAME_ATTRIBUTE"
- set_optional_property "ldap-member-attribute" "$LDAP_MEMBER_ATTRIBUTE"
- set_optional_property "ldap-user-search-filter" "$LDAP_USER_SEARCH_FILTER"
- set_optional_property "ldap-config-base-dn" "$LDAP_CONFIG_BASE_DN"
- set_optional_property "ldap-group-base-dn" "$LDAP_GROUP_BASE_DN"
- set_optional_property "ldap-group-search-filter" "$LDAP_GROUP_SEARCH_FILTER"
- set_optional_property "ldap-member-attribute-type" "$LDAP_MEMBER_ATTRIBUTE_TYPE"
- set_optional_property "ldap-group-name-attribute" "$LDAP_GROUP_NAME_ATTRIBUTE"
- set_optional_property "ldap-dereference-aliases" "$LDAP_DEREFERENCE_ALIASES"
- set_optional_property "ldap-follow-referrals" "$LDAP_FOLLOW_REFERRALS"
- set_optional_property "ldap-max-referral-hops" "$LDAP_MAX_REFERRAL_HOPS"
- set_optional_property "ldap-operation-timeout" "$LDAP_OPERATION_TIMEOUT"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/ldap/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-
-}
-
-##
-## Adds properties to guacamole.properties which select the LDAP
-## authentication provider, and configure it to connect to the specified LDAP
-## directory.
-##
-associate_radius() {
-
- # Verify required parameters are present
- if [ -z "$RADIUS_SHARED_SECRET" -o -z "$RADIUS_AUTH_PROTOCOL" ]; then
- cat <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using RADIUS server, you must provide each of the following environment
-variables:
-
- RADIUS_SHARED_SECRET The shared secret to use when talking to the
- RADIUS server.
-
- RADIUS_AUTH_PROTOCOL The authentication protocol to use when talking
- to the RADIUS server.
- Supported values are:
- pap, chap, mschapv1, mschapv2, eap-md5,
- eap-tls and eap-ttls.
-END
- exit 1;
- fi
-
- # Verify provided files do exist and are readable
- if [ -n "$RADIUS_KEY_FILE" -a ! -r "$RADIUS_KEY_FILE" ]; then
- cat <<END
-FATAL: Provided file RADIUS_KEY_FILE=$RADIUS_KEY_FILE does not exist
- or is not readable!
--------------------------------------------------------------------------------
-If you provide key or CA files you need to mount those into the container and
-make sure they are readable for the user in the container.
-END
- exit 1;
- fi
- if [ -n "$RADIUS_CA_FILE" -a ! -r "$RADIUS_CA_FILE" ]; then
- cat <<END
-FATAL: Provided file RADIUS_CA_FILE=$RADIUS_CA_FILE does not exist
- or is not readable!
--------------------------------------------------------------------------------
-If you provide key or CA files you need to mount those into the container and
-make sure they are readable for the user in the container.
-END
- exit 1;
- fi
- if [ "$RADIUS_AUTH_PROTOCOL" = "eap-ttls" -a -z "$RADIUS_EAP_TTLS_INNER_PROTOCOL" ]; then
- cat <<END
-FATAL: Authentication protocol "eap-ttls" specified but
- RADIUS_EAP_TTLS_INNER_PROTOCOL is not set!
--------------------------------------------------------------------------------
-When EAP-TTLS is used, this parameter specifies the inner (tunneled)
-protocol to use talking to the RADIUS server.
-END
- exit 1;
- fi
-
- # Update config file
- set_optional_property "radius-hostname" "$RADIUS_HOSTNAME"
- set_optional_property "radius-auth-port" "$RADIUS_AUTH_PORT"
- set_property "radius-shared-secret" "$RADIUS_SHARED_SECRET"
- set_property "radius-auth-protocol" "$RADIUS_AUTH_PROTOCOL"
- set_optional_property "radius-key-file" "$RADIUS_KEY_FILE"
- set_optional_property "radius-key-type" "$RADIUS_KEY_TYPE"
- set_optional_property "radius-key-password" "$RADIUS_KEY_PASSWORD"
- set_optional_property "radius-ca-file" "$RADIUS_CA_FILE"
- set_optional_property "radius-ca-type" "$RADIUS_CA_TYPE"
- set_optional_property "radius-ca-password" "$RADIUS_CA_PASSWORD"
- set_optional_property "radius-trust-all" "$RADIUS_TRUST_ALL"
- set_optional_property "radius-retries" "$RADIUS_RETRIES"
- set_optional_property "radius-timeout" "$RADIUS_TIMEOUT"
- set_optional_property "radius-eap-ttls-inner-protocol" "$RADIUS_EAP_TTLS_INNER_PROTOCOL"
- set_optional_property "radius-nas-ip" "$RADIUS_NAS_IP"
-
- set_optional_property \
- "radius-eap-ttls-inner-protocol" \
- "$RADIUS_EAP_TTLS_INNER_PROTOCOL"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/radius/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-}
-
-## Adds properties to guacamole.properties which select the OPENID
-## authentication provider, and configure it to connect to the specified OPENID
-## provider.
-##
-associate_openid() {
-
- # Verify required parameters are present
- if [ -z "$OPENID_AUTHORIZATION_ENDPOINT" ] || \
- [ -z "$OPENID_JWKS_ENDPOINT" ] || \
- [ -z "$OPENID_ISSUER" ] || \
- [ -z "$OPENID_CLIENT_ID" ] || \
- [ -z "$OPENID_REDIRECT_URI" ]
- then
- cat <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using an openid authentication, you must provide each of the following
-environment variables:
-
- OPENID_AUTHORIZATION_ENDPOINT The authorization endpoint (URI) of the OpenID service.
-
- OPENID_JWKS_ENDPOINT The endpoint (URI) of the JWKS service which defines
- how received ID tokens (JSON Web Tokens or JWTs)
- shall be validated.
-
- OPENID_ISSUER The issuer to expect for all received ID tokens.
-
- OPENID_CLIENT_ID The OpenID client ID which should be submitted
- to the OpenID service when necessary.
- This value is typically provided to you by the OpenID
- service when OpenID credentials are generated for your application.
-
- OPENID_REDIRECT_URI The URI that should be submitted to the OpenID service such that
- they can redirect the authenticated user back to Guacamole after
- the authentication process is complete. This must be the full URL
- that a user would enter into their browser to access Guacamole.
-END
- exit 1;
- fi
-
- # Update config file
- set_property "openid-authorization-endpoint" "$OPENID_AUTHORIZATION_ENDPOINT"
- set_property "openid-jwks-endpoint" "$OPENID_JWKS_ENDPOINT"
- set_property "openid-issuer" "$OPENID_ISSUER"
- set_property "openid-client-id" "$OPENID_CLIENT_ID"
- set_property "openid-redirect-uri" "$OPENID_REDIRECT_URI"
- set_optional_property "openid-username-claim-type" "$OPENID_USERNAME_CLAIM_TYPE"
- set_optional_property "openid-groups-claim-type" "$OPENID_GROUPS_CLAIM_TYPE"
- set_optional_property "openid-scope" "$OPENID_SCOPE"
- set_optional_property "openid-allowed-clock-skew" "$OPENID_ALLOWED_CLOCK_SKEW"
- set_optional_property "openid-max-token-validity" "$OPENID_MAX_TOKEN_VALIDITY"
- set_optional_property "openid-max-nonce-validity" "$OPENID_MAX_NONCE_VALIDITY"
-
- # Add required .jar files to GUACAMOLE_EXT
- # "1-{}" make it sorted as a first provider (only authentication)
- # so it can work together with the database providers (authorization)
- find /opt/guacamole/openid/ -name "*.jar" | awk -F/ '{print $NF}' | \
- xargs -I '{}' ln -s "/opt/guacamole/openid/{}" "${GUACAMOLE_EXT}/1-{}"
-
-}
-
-##
-## Adds properties to guacamole.properties which select the SAML
-## authentication provider, and configure it to connect to the specified SAML
-## provider.
-##
-
-associate_saml() {
-
- # Verify required parameters are present
- if [ -z "$SAML_IDP_METADATA_URL" ] && \
- [ -z "$SAML_ENTITY_ID" -o -z "$SAML_CALLBACK_URL" -o -z "$SAML_IDP_URL" ]
- then
- cat <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using a SAML authentication, you must provide either SAML_IDP_METADATA_URL
-or SAML_IDP_URL, SAML_ENTITY_ID and SAML_CALLBACK_URL environment variables:
-
- SAML_IDP_METADATA_URL The URI of the XML metadata file that from the SAML Identity
- Provider that contains all of the information the SAML
- extension needs in order to know how to authenticate with
- the IdP. This URI can either be a remote server (e.g. https://)
- or a local file on the filesystem (e.g. file://).
-
- SAML_IDP_URL The URL of the Identity Provider (IdP), which the user
- will be redirected to in order to authenticate.
-
- SAML_ENTITY_ID The entity ID of the Guacamole SAML client, which is
- generally the URL of the Guacamole server.
-
- SAML_CALLBACK_URL The URL that the IdP will use once authentication has
- succeeded to return to the Guacamole web application and
- provide the authentication details to the SAML extension.
-END
- exit 1;
- fi
-
- # Update config file
- set_optional_property "saml-idp-metadata-url" "$SAML_IDP_METADATA_URL"
- set_optional_property "saml-idp-url" "$SAML_IDP_URL"
- set_optional_property "saml-entity-id" "$SAML_ENTITY_ID"
- set_optional_property "saml-callback-url" "$SAML_CALLBACK_URL"
- set_optional_property "saml-strict" "$SAML_STRICT"
- set_optional_property "saml-debug" "$SAML_DEBUG"
- set_optional_property "saml-compress-request" "$SAML_COMPRESS_REQUEST"
- set_optional_property "saml-compress-response" "$SAML_COMPRESS_RESPONSE"
- set_optional_property "saml-group-attribute" "$SAML_GROUP_ATTRIBUTE"
-
- # Add required .jar files to GUACAMOLE_EXT
- # "1-{}" make it sorted as a first provider (only authentication)
- # so it can work together with the database providers (authorization)
- find /opt/guacamole/saml/ -name "*.jar" | awk -F/ '{print $NF}' | \
- xargs -I '{}' ln -s "/opt/guacamole/saml/{}" "${GUACAMOLE_EXT}/1-{}"
-
-}
-
-##
-## Adds properties to guacamole.properties which configure the TOTP two-factor
-## authentication mechanism.
-##
-associate_totp() {
- # Update config file
- set_optional_property "totp-issuer" "$TOTP_ISSUER"
- set_optional_property "totp-digits" "$TOTP_DIGITS"
- set_optional_property "totp-period" "$TOTP_PERIOD"
- set_optional_property "totp-mode" "$TOTP_MODE"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/totp/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-}
-
-##
-## Adds properties to guacamole.properties which configure the Duo two-factor
-## authentication service. Checks to see if all variables are defined
-##
-associate_duo() {
- # Verify required parameters are present
- if [ -z "$DUO_CLIENT_ID" ] || \
- [ -z "$DUO_CLIENT_SECRET" ] || \
- [ -z "$DUO_REDIRECT_URI" ]
- then
- cat <<END
-FATAL: Missing required environment variables
--------------------------------------------------------------------------------
-If using the Duo authentication extension, you must provide each of the
-following environment variables:
-
- DUO_API_HOSTNAME The hostname of the Duo API endpoint.
-
- DUO_CLIENT_ID The client id (or integration key) provided for Guacamole by Duo.
-
- DUO_CLIENT_SECRET The secret key provided for Guacamole by Duo.
-
- DUO_REDIRECT_URI The URI to redirect back to upon successful authentication.
-END
- exit 1;
- fi
-
- # Update config file
- set_property "duo-api-hostname" "$DUO_API_HOSTNAME"
- set_property "duo-client-id" "$DUO_CLIENT_ID"
- set_property "duo-client-secret" "$DUO_CLIENT_SECRET"
- set_property "duo-redirect-uri" "$DUO_REDIRECT_URI"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/duo/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-}
-
-##
-## Adds properties to guacamole.properties which configure the header
-## authentication provider.
-##
-associate_header() {
- # Update config file
- set_optional_property "http-auth-header" "$HTTP_AUTH_HEADER"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/header/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-}
-
-##
-## Adds properties to guacamole.properties witch configure the CAS
-## authentication service.
-##
-associate_cas() {
- # Verify required parameters are present
- if [ -z "$CAS_AUTHORIZATION_ENDPOINT" ] || \
- [ -z "$CAS_REDIRECT_URI" ]
- then
- cat <<END
-FATAL: Missing required environment variables
------------------------------------------------------------------------------------
-If using the CAS authentication extension, you must provide each of the
-following environment variables:
-
- CAS_AUTHORIZATION_ENDPOINT The URL of the CAS authentication server.
-
- CAS_REDIRECT_URI The URI to redirect back to upon successful authentication.
-
-END
- exit 1;
- fi
-
- # Update config file
- set_property "cas-authorization-endpoint" "$CAS_AUTHORIZATION_ENDPOINT"
- set_property "cas-redirect-uri" "$CAS_REDIRECT_URI"
- set_optional_property "cas-clearpass-key" "$CAS_CLEARPASS_KEY"
- set_optional_property "cas-group-attribute" "$CAS_GROUP_ATTRIBUTE"
- set_optional_property "cas-group-format" "$CAS_GROUP_FORMAT"
- set_optional_property "cas-group-ldap-base-dn" "$CAS_GROUP_LDAP_BASE_DN"
- set_optional_property "cas-group-ldap-attribute" "$CAS_GROUP_LDAP_ATTRIBUTE"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/cas/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-}
-
-##
-## Adds properties to guacamole.properties which configure the json
-## authentication provider.
-##
-associate_json() {
- # Update config file
- set_property "json-secret-key" "$JSON_SECRET_KEY"
- set_optional_property "json-trusted-networks" "$JSON_TRUSTED_NETWORKS"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/json/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-}
-
-##
-## Adds properties to guacamole.properties which configure the recording
-## storage extension.
-##
-associate_recordings() {
- # Update config file
- set_property "recording-search-path" "$RECORDING_SEARCH_PATH"
-
- # Add required .jar files to GUACAMOLE_EXT
- ln -s /opt/guacamole/recordings/guacamole-history-recording-storage-*.jar "$GUACAMOLE_EXT"
-}
-
-##
-## Sets up Tomcat's remote IP valve that allows gathering the remote IP
-## from headers set by a remote proxy
-## Upstream documentation: https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/catalina/valves/RemoteIpValve.html
-##
-enable_remote_ip_valve() {
- # Add <Valve> 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 <<END
-FATAL: Missing GUACD_HOSTNAME or "guacd" link.
--------------------------------------------------------------------------------
-Every Guacamole instance needs a corresponding copy of guacd running. To
-provide this, you must either:
-
-(a) Explicitly link that container with the link named "guacd".
-
-(b) If not using a Docker container for guacd, explicitly specify the TCP
- connection information using the following environment variables:
-
-GUACD_HOSTNAME The hostname or IP address of guacd. If not using a guacd
- Docker container and corresponding link, this environment
- variable is *REQUIRED*.
-
-GUACD_PORT The port on which guacd is listening for TCP connections.
- This environment variable is optional. If omitted, the
- standard guacd port of 4822 will be used.
-END
- exit 1;
-fi
-
-# Update config file
-set_property "guacd-hostname" "$GUACD_HOSTNAME"
-set_property "guacd-port" "$GUACD_PORT"
-
-# A comma-separated list of the identifiers of authentication providers that
-# should be allowed to fail internally without aborting the authentication process
-set_optional_property "skip-if-unavailable" "$SKIP_IF_UNAVAILABLE"
-
-
-#
-# Track which authentication backends are installed
-#
-
-INSTALLED_AUTH=""
-
-# Use MySQL if database specified
-if [ -n "$MYSQL_DATABASE" -o -n "$MYSQL_DATABASE_FILE" ]; then
- associate_mysql
- INSTALLED_AUTH="$INSTALLED_AUTH mysql"
-fi
-
-# Use PostgreSQL if database specified
-if [ -n "$POSTGRESQL_DATABASE" -o -n "$POSTGRESQL_DATABASE_FILE" ]; then
- associate_postgresql
- INSTALLED_AUTH="$INSTALLED_AUTH postgresql"
-fi
-
-# Use SQLServer if database specified
-if [ -n "$SQLSERVER_DATABASE" -o -n "$SQLSERVER_DATABASE_FILE" ]; then
- associate_sqlserver
- INSTALLED_AUTH="$INSTALLED_AUTH sqlserver"
-fi
-
-# Use LDAP directory if specified
-if [ -n "$LDAP_HOSTNAME" ]; then
- associate_ldap
- INSTALLED_AUTH="$INSTALLED_AUTH ldap"
-fi
-
-# Use RADIUS server if specified
-if [ -n "$RADIUS_SHARED_SECRET" ]; then
- associate_radius
- INSTALLED_AUTH="$INSTALLED_AUTH radius"
-fi
-
-# Use OPENID if specified
-if [ -n "$OPENID_AUTHORIZATION_ENDPOINT" ]; then
- associate_openid
- INSTALLED_AUTH="$INSTALLED_AUTH openid"
-fi
-
-# Use SAML if specified
-if [ -n "$SAML_IDP_METADATA_URL" ] || [ -n "$SAML_ENTITY_ID" -a -n "$SAML_CALLBACK_URL" ]; then
- associate_saml
- INSTALLED_AUTH="$INSTALLED_AUTH saml"
-fi
-
-# Use TOTP if specified.
-if [ "$TOTP_ENABLED" = "true" ]; then
- associate_totp
-fi
-
-# Use Duo if specified.
-if [ -n "$DUO_API_HOSTNAME" ]; then
- associate_duo
-fi
-
-# Use header if specified.
-if [ "$HEADER_ENABLED" = "true" ]; then
- associate_header
-fi
-
-# Use CAS if specified.
-if [ -n "$CAS_AUTHORIZATION_ENDPOINT" ]; then
- associate_cas
-fi
-
-# Use json-auth if specified.
-if [ -n "$JSON_SECRET_KEY" ]; then
- associate_json
- INSTALLED_AUTH="$INSTALLED_AUTH json"
-fi
-
-# Add in the history recording storage extension if configured
-if [ -n "$RECORDING_SEARCH_PATH" ]; then
- associate_recordings
-fi
-
-#
-# Validate that at least one authentication backend is installed
-#
-
-if [ -z "$INSTALLED_AUTH" -a -z "$GUACAMOLE_HOME_TEMPLATE" ]; then
- cat <<END
-FATAL: No authentication configured
--------------------------------------------------------------------------------
-The Guacamole Docker container needs at least one authentication mechanism in
-order to function, such as a MySQL database, PostgreSQL database, SQLServer
-database, LDAP directory or RADIUS server. Please specify at least the
-MYSQL_DATABASE or POSTGRESQL_DATABASE or SQLSERVER_DATABASE environment variables,
-or check Guacamole's Docker documentation regarding configuring LDAP and/or
-custom extensions.
-END
- exit 1;
-fi
-
-# Set extension priority if specified
-set_optional_property "extension-priority" "$EXTENSION_PRIORITY"
-
-# Use api-session-timeout if specified.
-if [ -n "$API_SESSION_TIMEOUT" ]; then
- associate_apisessiontimeout
-fi
-
-# Maximum number of bytes to accept within the entity body of any particular HTTP request
-set_optional_property "api-max-request-size" "$API_MAX_REQUEST_SIZE"
-
-# A comma-separated list of language keys to allow as display language
-# choices within the Guacamole interface
-set_optional_property "allowed-languages" "$ALLOWED_LANGUAGES"
-
-# If set to “true”, Guacamole will first evaluate its environment to obtain the value
-# for any given configuration property, before using a value specified in
-# guacamole.properties or falling back to a default value
-set_optional_property "enable-environment-properties" "$ENABLE_ENVIRONMENT_PROPERTIES"
-
-
-# Apply any overrides for default address ban behavior
-set_optional_property "ban-address-duration" "$BAN_ADDRESS_DURATION"
-set_optional_property "ban-max-addresses" "$BAN_MAX_ADDRESSES"
-set_optional_property "ban-max-invalid-attempts" "$BAN_MAX_INVALID_ATTEMPTS"
-
-# Always load guacamole-auth-ban extension (automatic banning can be disabled
-# through seting BAN_ADDRESS_DURATION to 0). As guacamole-auth-ban performs
-# its banning by handling a pre-authentication event, it is guaranteed to
-# perform its checks before all other auth processing and load order does not
-# matter.
-ln -s /opt/guacamole/ban/guacamole-auth-*.jar "$GUACAMOLE_EXT"
-
-# 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
-
-#
-# Finally start Guacamole (under Tomcat)
-#
-
-start_guacamole
diff --git a/guacamole-docker/build.d/000-build-and-install-guacamole.sh b/guacamole-docker/build.d/000-build-and-install-guacamole.sh
new file mode 100644
index 0000000..8c9c772
--- /dev/null
+++ b/guacamole-docker/build.d/000-build-and-install-guacamole.sh
@@ -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' \
+ ";"
+
diff --git a/guacamole-docker/build.d/010-map-guacamole-extensions.sh b/guacamole-docker/build.d/010-map-guacamole-extensions.sh
new file mode 100644
index 0000000..de12262
--- /dev/null
+++ b/guacamole-docker/build.d/010-map-guacamole-extensions.sh
@@ -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
+
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 0000000..6613dc3
--- /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 0000000..48707da
--- /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 0000000..cb56c4c
--- /dev/null
+++ b/guacamole-docker/entrypoint.d/000-migrate-legacy-variables.sh
@@ -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"
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 0000000..1db4d60
--- /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" <<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
+
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 0000000..cd86951
--- /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" <<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
+
diff --git a/guacamole-docker/entrypoint.d/700-configure-features.sh b/guacamole-docker/entrypoint.d/700-configure-features.sh
new file mode 100644
index 0000000..f1a2b1a
--- /dev/null
+++ b/guacamole-docker/entrypoint.d/700-configure-features.sh
@@ -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
+
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 0000000..e31b4e3
--- /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 0000000..ad6e5ff
--- /dev/null
+++ b/guacamole-docker/environment/REMOTE_IP_VALVE_/configure.sh
@@ -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"
+
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 255e4b0..a552993 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 @@
/**
* 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 {
@@ -64,6 +67,29 @@
}
/**
+ * 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 @@
+ "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 5b9a3d7..f2f313c 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 @@
* @see <a href="https://en.wikipedia.org/wiki/Tuna">Tuna (Wikipedia)</a>
*/
@PropertyValue("tuna")
+ @PropertyValue("yellowfin")
TUNA,
/**
@@ -135,6 +136,7 @@
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 @@
}
catch (GuacamoleException e) {
String message = e.getMessage();
- assertTrue(message.contains("\"mackerel\", \"salmon\", \"sardine\", \"trout\", \"tuna\""));
+ assertTrue(message.contains("\"mackerel\", \"salmon\", \"sardine\", \"trout\", \"tuna\", \"yellowfin\""));
}
}
diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java
index 1068477..d8e3f3d 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 @@
/**
* 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() {
@@ -106,6 +106,19 @@
};
/**
+ * 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.
*/
private Environment environment;
@@ -172,6 +185,23 @@
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);
@@ -210,13 +240,24 @@
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/SystemFileEnvironmentGuacamoleProperties.java b/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java
new file mode 100644
index 0000000..8b08252
--- /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;
+
+ }
+
+}
+
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 0000000..27bf0fe
--- /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 =
+ "<?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);
+ }
+
+}
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 accc4a9..a9e4ac3 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;
@@ -45,6 +51,19 @@
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
* the logging configuration.
*
@@ -54,26 +73,57 @@
public LogModule(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
protected void configure() {
- // Only load logback configuration if GUACAMOLE_HOME exists
- File guacamoleHome = environment.getGuacamoleHome();
- if (!guacamoleHome.isDirectory())
- return;
+ try (InputStream logbackConfiguration = getLogbackConfiguration()) {
- // 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();
- context.reset();
-
- try {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ context.reset();
// Initialize logback
JoranConfigurator configurator = new JoranConfigurator();
@@ -86,7 +136,11 @@
}
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 0b91a42..0000000
--- a/guacamole/src/main/resources/logback.xml
+++ /dev/null
@@ -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>
\ No newline at end of file