Hello, First of all, I wouldn't consider myself a Docker expert so it's very possible I'm doing something wrong. That said, I'm trying to set up a docker container that will use system CA certs to enable LDAPS auth. Since I wasn't able to find much info on this, I created my own hacky way to do it by creating a shared volume for /opt/java/openjdk/lib/security and then creating a service that injects my PKI root certs. It works, but I can't say I'm completely happy with it. After doing this, I noticed a file is created on the root of the drive called __cacert_entrypoint.sh. This file says there should be an env variable called USE_SYSTEM_CA_CERTS, which when set to true should inject any .crt cert mounted in /certificates. After finding this script, I found this post https://lists.apache.org/thread/4g8o2kb01srxv43nsk0f31csq556x4hq but he has very similar questions to mine . In the latest tag, as of right now, (https://hub.docker.com/layers/guacamole/guacamole/latest/images/sha256-5cc63aea0b427fb5fa12ff79f8c1cfbbbbfeee6a823131fa3aeafa49d8ba4188) it appears the script is called on line 15, the thing is it doesn't seem to do anything. In the docker logs I don't see anything logged from the script. I can't seem to find anything about what this __cacert_entrypoint.sh script is, or even the environment variable USE_SYSTEM_CA_CERTS in any of the docs. If I attach to the container and run the script manually, it appears to complete successfully, but the cert is not actually inserted for Guacamole/JVM to use. In the logs I still see the error "javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target". My best guess is that because I'm not running the container as root it's not doing completing correctly, even though the comments at the end of the script suggest that running as a non-root user should not matter (except for anything other than JVM, which I don't care about at the moment). Notice in my hacky solution that I'm adding the certs with the root user. Here is the output from the script:
$ ./__cacert_entrypoint.sh Using a temporary truststore at /tmp/tmp.VDvaEBw2fR Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/tmp/tmp.VDvaEBw2fR -Djavax.net.ssl.trustStorePassword=changeit Importing keystore /tmp/tmp.W62oZqncN9 to /tmp/tmp.VDvaEBw2fR... Entry for alias naverglobalrootcertificationauthority successfully imported. ...a whole bunch of standard CAs... Entry for alias hellenicacademicandresearchinstitutionsrootca2015 successfully imported. Import command completed: 146 entries successfully imported, 0 entries failed or cancelled Adding certificate with alias PKIIssuingCA to the JVM truststore Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/tmp/tmp.VDvaEBw2fR -Djavax.net.ssl.trustStorePassword=changeit Certificate was added to keystore Adding certificate with alias PKIOfflineCA to the JVM truststore Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/tmp/tmp.VDvaEBw2fR -Djavax.net.ssl.trustStorePassword=changeit Certificate was added to keystore Contents of __cacert_entrypoint.sh: #!/usr/bin/env bash # ------------------------------------------------------------------------------ # NOTE: THIS FILE IS GENERATED VIA "generate_dockerfiles.py" # # PLEASE DO NOT EDIT IT DIRECTLY. # ------------------------------------------------------------------------------ # # Licensed 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 # # https://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. # # This script defines `sh` as the interpreter, which is available in all POSIX environments. However, it might get # started with `bash` as the shell to support dotted.environment.variable.names which are not supported by POSIX, but # are supported by `sh` in some Linux flavours. set -e TMPDIR=${TMPDIR:-/tmp} # JDK truststore location JRE_CACERTS_PATH=$JAVA_HOME/lib/security/cacerts # Opt-in is only activated if the environment variable is set if [ -n "$USE_SYSTEM_CA_CERTS" ]; then if [ ! -w "$TMPDIR" ]; then echo "Using additional CA certificates requires write permissions to $TMPDIR. Cannot create truststore." exit 1 fi # Figure out whether we can write to the JVM truststore. If we can, we'll add the certificates there. If not, # we'll use a temporary truststore. if [ ! -w "$JRE_CACERTS_PATH" ]; then # We cannot write to the JVM truststore, so we create a temporary one JRE_CACERTS_PATH_NEW=$(mktemp) echo "Using a temporary truststore at $JRE_CACERTS_PATH_NEW" cp "$JRE_CACERTS_PATH" "$JRE_CACERTS_PATH_NEW" JRE_CACERTS_PATH=$JRE_CACERTS_PATH_NEW # If we use a custom truststore, we need to make sure that the JVM uses it export JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS} -Djavax.net.ssl.trustStore=${JRE_CACERTS_PATH} -Djavax.net.ssl.trustStorePassword=changeit" fi tmp_store=$(mktemp) # Copy full system CA store to a temporary location trust extract --overwrite --format=java-cacerts --filter=ca-anchors --purpose=server-auth "$tmp_store" > /dev/null # Add the system CA certificates to the JVM truststore. keytool -importkeystore -destkeystore "$JRE_CACERTS_PATH" -srckeystore "$tmp_store" -srcstorepass changeit -deststorepass changeit -noprompt > /dev/null # Clean up the temporary truststore rm -f "$tmp_store" # Import the additional certificate into JVM truststore for i in /certificates/*crt; do if [ ! -f "$i" ]; then continue fi tmp_dir=$(mktemp -d) BASENAME=$(basename "$i" .crt) # We might have multiple certificates in the file. Split this file into single files. The reason is that # `keytool` does not accept multi-certificate files csplit -s -z -b %02d.crt -f "$tmp_dir/$BASENAME-" "$i" '/-----BEGIN CERTIFICATE-----/' '{*}' for crt in "$tmp_dir/$BASENAME"-*; do # Extract the Common Name (CN) and Serial Number from the certificate CN=$(openssl x509 -in "$crt" -noout -subject -nameopt -space_eq | sed -n 's/^.*CN=\([^,]*\).*$/\1/p') SERIAL=$(openssl x509 -in "$crt" -noout -serial | sed -n 's/^serial=\(.*\)$/\1/p') # Check if an alias with the CN already exists in the keystore ALIAS=$CN if keytool -list -keystore "$JRE_CACERTS_PATH" -storepass changeit -alias "$ALIAS" >/dev/null 2>&1; then # If the CN already exists, append the serial number to the alias ALIAS="${CN}_${SERIAL}" fi echo "Adding certificate with alias $ALIAS to the JVM truststore" # Add the certificate to the JVM truststore keytool -import -noprompt -alias "$ALIAS" -file "$crt" -keystore "$JRE_CACERTS_PATH" -storepass changeit >/dev/null done done # Add additional certificates to the system CA store. This requires write permissions to several system # locations, which is not possible in a container with read-only filesystem and/or non-root container. if [ "$(id -u)" -eq 0 ]; then # Copy certificates from /certificates to the system truststore, but only if the directory exists and is not empty. # The reason why this is not part of the opt-in is because it leaves open the option to mount certificates at the # system location, for whatever reason. if [ -d /certificates ] && [ "$(ls -A /certificates 2>/dev/null)" ]; then cp -La /certificates/* /usr/local/share/ca-certificates/ fi update-ca-certificates else # If we are not root, we cannot update the system truststore. That's bad news for tools like `curl` and `wget`, # but since the JVM is the primary focus here, we can live with that. true fi fi # Let's provide a variable with the correct path for tools that want or need to use it export JRE_CACERTS_PATH exec "$@" Here's my docker-compose.yml (modified from https://github.com/boschkundendienst/guacamole-docker-compose) with the cert hack I mentioned above commented out. #https://github.com/boschkundendienst/guacamole-docker-compose/blob/master/docker-compose.yml # networks # create a network 'guac' in mode 'bridged' networks: guac: driver: bridge # services services: # guacd guacd: image: guacamole/guacd networks: - guac restart: always volumes: - ./drive:/drive:Z - ./record:/record:Z # postgres postgres: environment: PGDATA: /var/lib/postgresql/data/guacamole POSTGRES_DB: guacamole_db POSTGRES_PASSWORD: 'pass' POSTGRES_USER: guacamole_user image: postgres:15.2-alpine networks: - guac restart: always volumes: - ./init:/docker-entrypoint-initdb.d:Z - ./data:/var/lib/postgresql/data:Z - ./backups/postgres:/backups/postgres:Z # guacamole guacamole: group_add: - "1000" depends_on: - guacd - postgres environment: GUACD_HOSTNAME: guacd POSTGRESQL_DATABASE: guacamole_db POSTGRESQL_HOSTNAME: postgres POSTGRESQL_PASSWORD: 'pass' POSTGRESQL_USERNAME: guacamole_user RECORDING_SEARCH_PATH: /record LDAP_HOSTNAME: "guac" LDAP_PORT: 636 LDAP_ENCRYPTION_METHOD: ssl LDAP_USER_BASE_DN: "OU=some-ou,DC=some-domain" LDAP_USER_SEARCH_FILTER: "(&(objectClass=user)(memberOf:=CN=some-group,OU=some-ou,DC=some-domain))" LDAP_USERNAME_ATTRIBUTE: sAMAccountName LDAP_SEARCH_BIND_DN: "CN=some-account,OU=some-ou,DC=some-domain" LDAP_SEARCH_BIND_PASSWORD: "pass" USE_SYSTEM_CA_CERTS: true image: guacamole/guacamole networks: - guac volumes: - ./record:/record:Z - ./backups/guacamole:/backups/guacamole:Z #- ca_certs:/opt/java/openjdk/lib/security:z - ./FullChain.pem:/certificates/FullChain.crt:Z ports: - 8080:8080/tcp # Guacamole is on :8080/guacamole, not /. restart: always # guacamole_ca_certs # guacamole_ca_certs: # user: root # depends_on: # - guacamole # image: guacamole/guacamole # volumes: # - ./FullChain.pem:/etc/ssl/certs/FullChain.pem:Z # - ca_certs:/opt/java/openjdk/lib/security:z # entrypoint: [ "sh", "-c" ] # command: [ "keytool -importcert -alias domain -file /etc/ssl/certs/FullChain.pem -keystore /opt/java/openjdk/lib/security/cacerts -storepass changeit -noprompt" ] #volumes: # ca_certs: Note: The message and attachment(s) to this email are intended for the use of the individual or entity to which it is addressed, and may contain information that is privileged, confidential and exempt from disclosure under applicable law. If the reader of this message is not the intended recipient, you are hereby notified that any dissemination, distribution, printing or copying of this communication is strictly prohibited. If you received this in error, please contact the sender and delete the original message, any attachment(s) and copies. Thank you for your cooperation.
