Skip to content

io7m-com/adelaide

Repository files navigation

adelaide

Maven Central Maven Central (snapshot) Codecov Java Version

com.io7m.adelaide

JVM Platform Status
OpenJDK (Temurin) Current Linux Build (OpenJDK (Temurin) Current, Linux)
OpenJDK (Temurin) LTS Linux Build (OpenJDK (Temurin) LTS, Linux)
OpenJDK (Temurin) Current Windows Build (OpenJDK (Temurin) Current, Windows)
OpenJDK (Temurin) LTS Windows Build (OpenJDK (Temurin) LTS, Windows)

adelaide

Better ActiveMQ Artemis OCI images.

Motivation

The existing Artemis OCI images have a number of problems that are addressed by the adelaide images.

  • The images expect users to generate configuration files and then use them in the broker instances. This makes the container the "owner" of the configuration files instead of the files being a read-only input to the container, and makes it difficult to keep them in version control. Additionally, the images seem to be designed under the assumption that a user might want to run more than one broker in a container instance. Nobody sensible wants to do this. The adelaide images expose a single /data volume for the single broker instance supported by the container, and expose a single /data/etc volume to allow for configuration files to be read-only mounted in the container, and managed externally.

  • The images expect to be run under Docker, and therefore do the usual idiotic "run as a separate UID inside the container" dance. This is a fundamental design flaw of Docker and is one of many reasons to exclusively use podman instead. The adelaide images do not do any manipulation of UIDs or GIDs; if you run the adelaide image as UID 0 on the host, Artemis will run as UID 0 inside the container. Simply run podman as a non-root user on the host, and everything inside the container is guaranteed to run without privileges of any kind. The adelaide images do not support Docker; this is a feature and not a bug.

  • The adelaide images are free of any platform-specific complexity. Noone sensible is hosting message broker containers on any platform other than Linux.

  • The adelaide images are designed to be run with an entirely read-only filesystem (aside from the read/write mounted broker instance directory). This is intended to allow for greater reliability and security.

Features

  • Runs rootless with a read-only root directory!
  • Version control your broker configuration files!
  • Easy TLS reloading for ACME.
  • ISC license.

Usage

Run podman as an unprivileged user, with an invocation similar to:

podman run \
  --read-only \
  --volume '/path/to/host/tls:/tls:ro,z' \
  --volume '/path/to/broker/data:/data:Z,rw' \
  --volume '/path/to/broker/etc:/data/etc:Z,ro' \
  --publish '...:61616:61616/tcp' \
  quay.io/io7mcom/adelaide:${VERSION}

Naturally, the exact directories you mount, and the exact TCP ports you publish is dependent on your broker configuration.

Use the following volume mounts:

Mount Description
/data Persistent broker data (the "broker instance")
/data/etc Broker configuration files (such as broker.xml)
/tls A directory containing keystores.

The /data mount contains the persistent state of the one-and-only broker instance used by the container.

The /data/etc mount should be mounted read-only and should contain your version-controlled broker configuration files (such as broker.xml).

The /tls mount should contain keystores and truststores.

For extra security, run podman run with the --read-only option; the images are designed such that no part of the filesystem except for /data is required to be writable.

TLS Reloading

The image provides a /broker-tls-reload.sh script that can be executed inside the container to instruct Artemis to reload TLS certificates. We'll assume that the broker container is being run under the _artemis user account on the host throughout this example.

# /broker-tls-reload.sh
usage: user password broker-name acceptor-name

We'll start by assuming that we have some client on the host that knows how to write PEM-formatted certificates into some directory (any ACME client can do this). We'll then periodically (hourly is sufficient) convert those PEM-formatted certificates to a PKCS12 formatted keystore and write the keystore to a path that is visible to the broker running inside the container. We'll then tell the broker to reload its own keystore. The steps are as follows:

  1. Configure your broker to read a keystore from /tls/brokerKeystore.pkcs12. For example, in the broker.xml:
  <acceptor name="artemis">
    tcp://0.0.0.0:60000?protocols=AMQP;sslEnabled=true;keyStorePath=/tls/brokerKeystore.p12;keyStorePassword=changeit;trustStorePath=/opt/java/openjdk/lib/security/cacerts;trustStorePassword=changeit
  </acceptor>
  1. Write a script to generate /tls/brokerKeystore.p12 from outside of the container. For the sake of example, we'll refer to this script as /usr/local/bin/regenerate-keystore.sh on the host. For example, if an ACME client is placing certificates into the host directory /etc/certificates/example.com, and the directory /containers/messaging01/tls is mounted at /tls in the container, then it is straightforward to write a script to produce a PKCS12 keystore:
#!/bin/sh -ex

CERTIFICATE_BASE="/etc/certificates/example.com"
OUTPUT="/containers/messaging01/tls"

openssl pkcs12 \
  -export \
  -out "${OUTPUT}/brokerKeystore.p12.tmp" \
  -in "${CERTIFICATE_BASE}/full_chain.pem" \
  -inkey "${CERTIFICATE_BASE}/private.key" \
  -passout "pass:changeit"

chown _artemis:_artemis "${OUTPUT}/brokerKeystore.p12.tmp"
mv "${OUTPUT}/brokerKeystore.p12.tmp" "${OUTPUT}/brokerKeystore.p12"
  1. Set up a service to periodically call the script to generate the keystore, and then call /broker-tls-reload.sh inside the container to reload certificates. In this example, we assume that the container is called messaging01, and it exposes a broker called Messaging01 with an acceptor called artemis. It also has an admin user called grouch with a password some-very-long-password-here. Naturally, all of these values will likely be different for your particular broker installation.
[Unit]
Description=Messaging01 TLS Service

[Service]
Type=oneshot
User=_artemis
Group=_artemis

ExecStart=+/bin/sh /usr/local/bin/regenerate-keystore.sh
ExecStart=/usr/bin/podman      \
  exec                         \
  -i                           \
  -t                           \
  messaging01                  \
  /broker-tls-reload.sh        \
  grouch                       \
  some-very-long-password-here \
  Messaging01                  \
  artemis

[Install]
WantedBy=multi-user.target
[Unit]
Description=Messaging01 TLS timer

[Timer]
OnCalendar=*-*-* *:00/59:00
Persistent=true

[Install]
WantedBy=timers.target