Skip to content

Commit

Permalink
feat(jmx): custom entrypoint to support copying TLS certs into trusts…
Browse files Browse the repository at this point in the history
…tore for JMX (#330)
  • Loading branch information
andrewazores authored Apr 18, 2024
1 parent 3331284 commit 2c22fac
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 2 deletions.
5 changes: 5 additions & 0 deletions compose/cryostat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,3 +40,7 @@ services:
retries: 3
start_period: 30s
timeout: 5s

volumes:
jmxtls_cfg:
external: true
13 changes: 13 additions & 0 deletions schema/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions smoketest.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down
17 changes: 16 additions & 1 deletion src/main/docker/Dockerfile.jvm
Original file line number Diff line number Diff line change
Expand Up @@ -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
82 changes: 82 additions & 0 deletions src/main/docker/include/entrypoint.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash

set -e

DIR="$(dirname "$(realpath "$0")")"
source "${DIR}/genpass.bash"

function banner() {
echo "+------------------------------------------+"
printf "| %-40s |\n" "$(date)"
echo "| |"
printf "| %-40s |\n" "$@"
echo "+------------------------------------------+"
}

PWFILE="/tmp/jmxremote.password"
USRFILE="/tmp/jmxremote.access"
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() {
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
5 changes: 5 additions & 0 deletions src/main/docker/include/genpass.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

genpass() {
< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32
}
24 changes: 24 additions & 0 deletions src/main/docker/include/truststore-setup.bash
Original file line number Diff line number Diff line change
@@ -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}"
2 changes: 2 additions & 0 deletions src/main/java/io/cryostat/ConfigProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
53 changes: 53 additions & 0 deletions src/main/java/io/cryostat/security/TrustStore.java
Original file line number Diff line number Diff line change
@@ -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("")
public class TrustStore {

@ConfigProperty(name = ConfigProperties.SSL_TRUSTSTORE_DIR)
java.nio.file.Path trustStoreDir;

@Inject Logger logger;

@Blocking
@GET
@Path("/api/v3/tls/certs")
@Produces(MediaType.APPLICATION_JSON)
public List<String> listCerts() throws IOException {
return Files.walk(trustStoreDir)
.map(java.nio.file.Path::toFile)
.filter(File::isFile)
.map(File::getPath)
.toList();
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2c22fac

Please sign in to comment.