From 1250adbb9340451d03332dae0a1a34b2e4856701 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 15 Mar 2024 15:53:55 -0400 Subject: [PATCH 1/7] feat(jmx): custom entrypoint to support copying TLS certs into truststore for JMX --- compose/cryostat.yml | 5 ++ smoketest.bash | 9 ++ src/main/docker/Dockerfile.jvm | 17 +++- src/main/docker/include/entrypoint.bash | 83 +++++++++++++++++++ src/main/docker/include/genpass.bash | 5 ++ src/main/docker/include/truststore-setup.bash | 24 ++++++ 6 files changed, 142 insertions(+), 1 deletion(-) create mode 100755 src/main/docker/include/entrypoint.bash create mode 100755 src/main/docker/include/genpass.bash create mode 100755 src/main/docker/include/truststore-setup.bash diff --git a/compose/cryostat.yml b/compose/cryostat.yml index c42fb185d..f77c3167a 100644 --- a/compose/cryostat.yml +++ b/compose/cryostat.yml @@ -14,6 +14,7 @@ services: image: ${CRYOSTAT_IMAGE:-quay.io/cryostat/cryostat:3.0.0-snapshot} volumes: - ${XDG_RUNTIME_DIR}/podman/podman.sock:/run/user/1000/podman/podman.sock:Z + - jmxtls_cfg:/truststore:U security_opt: - label:disable hostname: cryostat3 @@ -39,3 +40,7 @@ services: retries: 3 start_period: 30s timeout: 5s + +volumes: + jmxtls_cfg: + external: true diff --git a/smoketest.bash b/smoketest.bash index 6e88e733a..9e77c6478 100755 --- a/smoketest.bash +++ b/smoketest.bash @@ -180,6 +180,8 @@ cleanup() { ${container_engine} rm localstack_cfg_helper || true ${container_engine} volume rm localstack_cfg || true fi + ${container_engine} rm jmxtls_cfg_helper || true + ${container_engine} volume rm jmxtls_cfg || true truncate -s 0 "${HOSTSFILE}" for i in "${PIDS[@]}"; do kill -0 "${i}" && kill "${i}" @@ -212,6 +214,13 @@ if [ "${s3}" = "localstack" ]; then createLocalstackCfgVolume fi +createJmxTlsCertVolume() { + "${container_engine}" volume create jmxtls_cfg + "${container_engine}" container create --name jmxtls_cfg_helper -v jmxtls_cfg:/truststore busybox + "${container_engine}" cp "${DIR}/truststore" jmxtls_cfg_helper:/truststore +} +createJmxTlsCertVolume + setupUserHosts() { # This requires https://github.com/figiel/hosts to work. See README. truncate -s 0 "${HOSTSFILE}" diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm index 8f5b902da..db1431403 100644 --- a/src/main/docker/Dockerfile.jvm +++ b/src/main/docker/Dockerfile.jvm @@ -88,11 +88,26 @@ LABEL io.cryostat.component=cryostat3 ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" -ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] +ENTRYPOINT [ "/deployments/app/entrypoint.bash", "/opt/jboss/container/java/run/run-java.sh" ] # We make distinct layers so if there are application changes the library layers can be re-used COPY --chown=185 src/main/docker/include/cryostat.jfc /usr/lib/jvm/jre/lib/jfr/ +COPY --chown=185 src/main/docker/include/genpass.bash /deployments/app/ +COPY --chown=185 src/main/docker/include/entrypoint.bash /deployments/app/ +COPY --chown=185 src/main/docker/include/truststore-setup.bash /deployments/app/ COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ COPY --chown=185 target/quarkus-app/*.jar /deployments/ COPY --chown=185 target/quarkus-app/app/ /deployments/app/ COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +ENV CONF_DIR=/opt/cryostat.d +ENV SSL_TRUSTSTORE=$CONF_DIR/truststore.p12 \ + SSL_TRUSTSTORE_PASS_FILE=$CONF_DIR/truststore.pass + +USER root +RUN mkdir -p $CONF_DIR \ + && chmod -R g=u $CONF_DIR \ + && chown jboss:root $CONF_DIR +USER 185 + +RUN /deployments/app/truststore-setup.bash diff --git a/src/main/docker/include/entrypoint.bash b/src/main/docker/include/entrypoint.bash new file mode 100755 index 000000000..1b0b46a32 --- /dev/null +++ b/src/main/docker/include/entrypoint.bash @@ -0,0 +1,83 @@ +#!/bin/bash + +set -e + +DIR="$(dirname "$(realpath "$0")")" +source "${DIR}/genpass.bash" + +function banner() { + echo "+------------------------------------------+" + printf "| %-40s |\n" "$(date)" + echo "| |" + printf "| %-40s |\n" "$@" + echo "+------------------------------------------+" +} + +USRFILE="/tmp/jmxremote.access" +PWFILE="/tmp/jmxremote.password" +function createJmxCredentials() { + if [ -z "$CRYOSTAT_RJMX_USER" ]; then + CRYOSTAT_RJMX_USER="cryostat" + fi + if [ -z "$CRYOSTAT_RJMX_PASS" ]; then + CRYOSTAT_RJMX_PASS="$(genpass)" + fi + + echo -n "$CRYOSTAT_RJMX_USER $CRYOSTAT_RJMX_PASS" > "$PWFILE" + chmod 400 "$PWFILE" + echo -n "$CRYOSTAT_RJMX_USER readwrite" > "$USRFILE" + chmod 400 "$USRFILE" +} + +function importTrustStores() { + echo "Running as id:$(id -u) group:$(id -g)" + if [ -z "$CONF_DIR" ]; then + CONF_DIR="/opt/cryostat.d" + fi + if [ -z "$SSL_TRUSTSTORE_DIR" ]; then + SSL_TRUSTSTORE_DIR="/truststore" + fi + + if [ ! -d "$SSL_TRUSTSTORE_DIR" ]; then + banner "$SSL_TRUSTSTORE_DIR does not exist; no certificates to import" + return 0 + elif [ ! "$(ls -A "$SSL_TRUSTSTORE_DIR")" ]; then + banner "$SSL_TRUSTSTORE_DIR is empty; no certificates to import" + return 0 + fi + + SSL_TRUSTSTORE_PASS="$(cat "${SSL_TRUSTSTORE_PASS_FILE:-$CONF_DIR/truststore.pass}")" + + find "$SSL_TRUSTSTORE_DIR" -type f | while IFS= read -r cert; do + echo "Importing certificate $cert ..." + + keytool -importcert -v \ + -noprompt \ + -alias "imported-$(basename "$cert")" \ + -trustcacerts \ + -keystore "${SSL_TRUSTSTORE:-$CONF_DIR/truststore.p12}" \ + -file "$cert"\ + -storepass "$SSL_TRUSTSTORE_PASS" + done + + FLAGS+=( + "-Djavax.net.ssl.trustStore=$SSL_TRUSTSTORE" + "-Djavax.net.ssl.trustStorePassword=$SSL_TRUSTSTORE_PASS" + ) +} + +FLAGS=() +importTrustStores + +if [ "$CRYOSTAT_DISABLE_JMX_AUTH" = "true" ]; then + banner "JMX Auth Disabled" + FLAGS+=("-Dcom.sun.management.jmxremote.authenticate=false") +else + createJmxCredentials + FLAGS+=("-Dcom.sun.management.jmxremote.authenticate=true") + FLAGS+=("-Dcom.sun.management.jmxremote.password.file=$PWFILE") + FLAGS+=("-Dcom.sun.management.jmxremote.access.file=$USRFILE") +fi + +export JAVA_OPTS_APPEND="${JAVA_OPTS_APPEND} ${FLAGS[*]}" +exec $1 diff --git a/src/main/docker/include/genpass.bash b/src/main/docker/include/genpass.bash new file mode 100755 index 000000000..ba1557267 --- /dev/null +++ b/src/main/docker/include/genpass.bash @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +genpass() { + < /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32 +} diff --git a/src/main/docker/include/truststore-setup.bash b/src/main/docker/include/truststore-setup.bash new file mode 100755 index 000000000..ab4d80cfa --- /dev/null +++ b/src/main/docker/include/truststore-setup.bash @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e + +DIR="$(dirname "$(realpath "$0")")" +source "${DIR}/genpass.bash" + +SSL_TRUSTSTORE_PASS="$(genpass)" + +echo "$SSL_TRUSTSTORE_PASS" > "$SSL_TRUSTSTORE_PASS_FILE" + +trap "cd -" EXIT +cd "$CONF_DIR" + +keytool -importkeystore \ + -noprompt \ + -storetype PKCS12 \ + -srckeystore /usr/lib/jvm/jre-17-openjdk/lib/security/cacerts \ + -srcstorepass changeit \ + -destkeystore "$SSL_TRUSTSTORE" \ + -deststorepass "$SSL_TRUSTSTORE_PASS" + +chmod 664 "${SSL_TRUSTSTORE}" +chmod 640 "${SSL_TRUSTSTORE_PASS_FILE}" From 2e116dae2fca4ead4710b71aa83cb753ac4aa11b Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 22 Mar 2024 16:11:30 -0400 Subject: [PATCH 2/7] remove troubleshooting echo --- src/main/docker/include/entrypoint.bash | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/docker/include/entrypoint.bash b/src/main/docker/include/entrypoint.bash index 1b0b46a32..13384bec3 100755 --- a/src/main/docker/include/entrypoint.bash +++ b/src/main/docker/include/entrypoint.bash @@ -30,7 +30,6 @@ function createJmxCredentials() { } function importTrustStores() { - echo "Running as id:$(id -u) group:$(id -g)" if [ -z "$CONF_DIR" ]; then CONF_DIR="/opt/cryostat.d" fi From e26c8c404ce4e657968dac1f1041b8014f14d146 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 16 Apr 2024 01:07:48 -0400 Subject: [PATCH 3/7] reorder --- src/main/docker/include/entrypoint.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/docker/include/entrypoint.bash b/src/main/docker/include/entrypoint.bash index 13384bec3..892f4c71a 100755 --- a/src/main/docker/include/entrypoint.bash +++ b/src/main/docker/include/entrypoint.bash @@ -13,8 +13,8 @@ function banner() { echo "+------------------------------------------+" } -USRFILE="/tmp/jmxremote.access" PWFILE="/tmp/jmxremote.password" +USRFILE="/tmp/jmxremote.access" function createJmxCredentials() { if [ -z "$CRYOSTAT_RJMX_USER" ]; then CRYOSTAT_RJMX_USER="cryostat" From c029aff03b288bbf550a54a4c9867d95f99e5ed0 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 16 Apr 2024 01:32:30 -0400 Subject: [PATCH 4/7] add endpoint for listing truststore contents filenames --- .../java/io/cryostat/ConfigProperties.java | 2 + .../java/io/cryostat/security/TrustStore.java | 53 +++++++++++++++++++ src/main/resources/application.properties | 5 ++ 3 files changed, 60 insertions(+) create mode 100644 src/main/java/io/cryostat/security/TrustStore.java diff --git a/src/main/java/io/cryostat/ConfigProperties.java b/src/main/java/io/cryostat/ConfigProperties.java index f69efd3f1..0078a977b 100644 --- a/src/main/java/io/cryostat/ConfigProperties.java +++ b/src/main/java/io/cryostat/ConfigProperties.java @@ -51,4 +51,6 @@ public class ConfigProperties { public static final String STORAGE_TRANSIENT_ARCHIVES_ENABLED = "storage.transient-archives.enabled"; public static final String STORAGE_TRANSIENT_ARCHIVES_TTL = "storage.transient-archives.ttl"; + + public static final String SSL_TRUSTSTORE_DIR = "ssl.truststore.dir"; } diff --git a/src/main/java/io/cryostat/security/TrustStore.java b/src/main/java/io/cryostat/security/TrustStore.java new file mode 100644 index 000000000..1776d0221 --- /dev/null +++ b/src/main/java/io/cryostat/security/TrustStore.java @@ -0,0 +1,53 @@ +/* + * Copyright The Cryostat Authors. + * + * 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 + * + * 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 io.cryostat.security; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +import io.cryostat.ConfigProperties; + +import io.smallrye.common.annotation.Blocking; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +@Path("/api/v3") +public class TrustStore { + + @ConfigProperty(name = ConfigProperties.SSL_TRUSTSTORE_DIR) + java.nio.file.Path trustStoreDir; + + @Inject Logger logger; + + @Blocking + @GET + @Path("tls/certs") + @Produces(MediaType.APPLICATION_JSON) + public List listCerts() throws IOException { + return Files.walk(trustStoreDir) + .map(java.nio.file.Path::toFile) + .filter(File::isFile) + .map(File::getPath) + .toList(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6d6ab697b..6091cdbac 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -32,6 +32,11 @@ cryostat.http.proxy.host=${quarkus.http.host} cryostat.http.proxy.port=${quarkus.http.port} cryostat.http.proxy.path=/ +conf-dir=/opt/cryostat.d +ssl.truststore=${conf-dir}/truststore.p12 +ssl.truststore.dir=/truststore +ssl.truststore.pass-file=${conf-dir}/truststore.pass + quarkus.http.auth.proactive=false quarkus.http.host=0.0.0.0 quarkus.http.port=8181 From 69a26f056905c62427a89883648fd637e06fe1a6 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 16 Apr 2024 09:39:47 -0400 Subject: [PATCH 5/7] correct path --- src/main/java/io/cryostat/security/TrustStore.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/cryostat/security/TrustStore.java b/src/main/java/io/cryostat/security/TrustStore.java index 1776d0221..26f660e40 100644 --- a/src/main/java/io/cryostat/security/TrustStore.java +++ b/src/main/java/io/cryostat/security/TrustStore.java @@ -31,7 +31,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; -@Path("/api/v3") +@Path("") public class TrustStore { @ConfigProperty(name = ConfigProperties.SSL_TRUSTSTORE_DIR) @@ -41,7 +41,7 @@ public class TrustStore { @Blocking @GET - @Path("tls/certs") + @Path("/api/v3/tls/certs") @Produces(MediaType.APPLICATION_JSON) public List listCerts() throws IOException { return Files.walk(trustStoreDir) From dbffdcc3f0a2842a871a6fe451bb2c991c1e475c Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 16 Apr 2024 13:12:50 -0400 Subject: [PATCH 6/7] remove unused header exposition --- src/main/resources/application-dev.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 08e564fbe..0467ceee7 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -6,7 +6,7 @@ quarkus.http.cors=true # quarkus.http.cors.origins=http://localhost:9000,http://0.0.0.0:9000 quarkus.http.cors.origins=http://localhost:9000 quarkus.http.cors.access-control-allow-credentials=true -quarkus.http.cors.exposed-headers=X-JMX-Authorization,X-JMX-Authenticate,X-WWW-Authenticate +quarkus.http.cors.exposed-headers=X-WWW-Authenticate # quarkus.http.cors.methods=GET,PUT,POST,PATCH,OPTIONS # quarkus.http.cors.access-control-max-age=1s From 5943e3b58916c08ad65e2de069f083d5a2fd943d Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 16 Apr 2024 14:09:22 -0400 Subject: [PATCH 7/7] update schema --- schema/openapi.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/schema/openapi.yaml b/schema/openapi.yaml index b7db9ef33..fcda791f2 100644 --- a/schema/openapi.yaml +++ b/schema/openapi.yaml @@ -2184,6 +2184,19 @@ paths: - SecurityScheme: [] tags: - Reports + /api/v3/tls/certs: + get: + responses: + "200": + content: + application/json: + schema: + items: + type: string + type: array + description: OK + tags: + - Trust Store /health: get: responses: