Good morning all,
I’m posting this both to collect my thoughts in one place for something that concerns multiple different source code repositories (and so has no single convenient Git repository to which I could push it), and to get community feedback from any potential Cascade operators out there who would want to run it inside a container on their needs and any opinions and/or issues with what I describe here.
This is not a ready-to-run recipe for running Cascade using containers, please don’t ask for support on how to use this, though of course we do value feedback on anything you think is missing, incorrect or questionable.
Background & inspiration
These thoughts arose from some manual testing I did with Nitrokey NetHSM while reviewing remove obsolete note about NetHSM by jans23 · Pull Request #621 · NLnetLabs/cascade · GitHub . The Nitrokey NetHSM is offered in container form for testing and prior to the issue described in PR #621 @jpmens kindly contributed documentation on Integrating (Cascade) with a Nitrokey NetHSM. However, that documentation was written for a Debian 12 host while my work development machine runs Fedora.
Rather than run yet another instance of Cascade from sources or RPMs on my work machine, given that NetHSM is already avaiable in containerized form, I decided this would be a good excuse to create a Docker Compose YAML description of a private test network running Cascade, Cascade-HSM-Bridge and Nitrokey NetHSM as separate containers, as we know that there are customers that want to run Cascade in containerized form.
I also knew that there are some existing resources out there I could use to help me automate the setup of the Nitrokey NetHSM, including a provisioning script by Nitrokey themselves and Niek Willems of SIDN also has projects online based around NetHSM from which I could take inspiration from the “Stiab: DNSSEC Signer street-in-a-box” NetHSM provisioning script and CENTR tech 52 HSM Workshop.
So, let’s think a moment about what it means to run Cascade inside a container, e.g. using Docker (with which I have by far the most experience).
Typical architecture when using containers
Some things to keep in mind with Docker containers is that they are not complete operating systems, they don’t have systemd for example, or syslog, and the general advice is to run one process per container. Of course you can run multiple processes including syslog and systemd in a single container if you really want to, but it’s not what I’m going to try doing here.
Which components do we need to run Cascade when using containers?
For the Cascade daemon itself we need a Docker image that provides and runs the cascaded daemon binary and makes available to it its configuration file, data directories, and enables logging by Cascade to standard out/err (as is typical for containers).
This image will also need a few more things:
-
The
dnstbinary which Cascade invokes. -
Optional: a way for users to mount their own review hook scripts into the container for Cascade to invoke.
-
The above imply that we also need the
tinibinary to run as PID 0 (for proper child process reaping and signal handling), or for operators to know that they must invoke Docker or other container host with--init(which also runstinias PID 0 but without includingtiniin the container image). -
Support for mounting files into the container from the host, e.g. for:
- Supplying and editing policy files in order for
cascade policy reloadto work. - Enabling
cascade zone add --source path/to/fileto work (if wanted, e.g. instead of using A/IXFR to receive the input zone).
- Supplying and editing policy files in order for
-
Expose some network UDP/TCP ports:
- The Cascade publication server port (UDP+TCP): for DNS queries to be able to reach Cascade from outside the container, e.g. from upstream primaries, downstream secondaries, monitoring tools and operators.
- The Cascade control port (TCP):
- Prometheus metric gathering.
- Health checking.
- Control by a Cascade client binary running outside the container.
How will we configure and control the Cascade daemon?
The Cascade client binary needs to be run somewhere so that operators can configure and control Cascade. We could:
-
Require operators to exec a shell inside the Cascade daemon container and run the
cascadeclient binary there, OR -
Expect operators to separately have a copy of the
cascadeclient binary on an operator machine and connect to the Cascade control port of the container running Cascade, OR -
Run an additional container that provisions other containers such as NetHSM and also offers the Cascade binary for operators to shell in and use.
How can we get dnst into the Cascade daemon image?
- Build from sources. This would be slightly awkward as normally, e.g. how our Ploutos packaging framework expects, a single Rust project Git repo is built and generated binaries included in the final Docker image, but
dnstsources are in a separate repo to that of Cascade. - Pre-build the
dnstbinary outside the container and include it during image building. This would require the host to be the same architecture and use the same LIBC dependency as the container image. - Install
dnstusing a DEB or RPM package that we publish. This would not match well with the fact that we typically use Alpine as our base image for Docker images that we publish (for its tiny size). - Use the Docker
FROM --imagedirective to copy thednstbinary from an existing publisheddnstDocker image. This would require aDockerfilein thednstproject as well. This might at first seem odd asdnstis not a service you leave running, but there are examples of one shot containers that run a command produce an output. We could offer such an image and we could then copy the binary from inside it. If it too used Alphine, as we usually do, it would be compatible with the Cascade container image. - Mount a
dnstbinary from the host into the container. This would require the binary to be compatible with the container.
The above would be enough to use Cascade with on-disk keys, but not to use Cascade with an HSM.
What extra bits are needed to communicate with an HSM?
- An image from which to deploy a container running the
cascade-hsm-bridgedaemon binary, and associated configuration file and logging to stdout/stderr (as is typical for containers). - A means to provide the HSM PKCS#11 module to the bridge daemon. The module would need to be binary compatible for the O/S and architecture, and have any supporting libraries, config files, environment variables, etc. i.e. the dependencies needed by the specific HSM type to run which varies from HSM to HSM. Often those files are only available to paying customers, i.e. we couldn’t provide images for all HSM we support even if we wanted to and had the resources to maintain them all, but we could offer a base image and a demonstration derived image based on something open like NetHSM. (or SoftHSM, Krypotic and probably others)
This could be achieved by offering a “base” image for the bridge that operators would extend with their own actual image containing their specific HSM PKCS#11 module and dependencies.
Such a base image might need to be Debian/Ubuntu and also available as RedHat compatible, so that operators whose PKCS#11 module, and possibly also required O/S package dependencies, are packaged as DEB or RPM.
Alternatively we could decide NOT to offer an image for the bridge but only document what such an image would need to do and leave others to create HSM specific images.
We would also need a way to provision the HSM for first use, e.g. setup user accounts and passwords. This could be hard-coded into the HSM image on startup or it can be done (assuming remote control is possible) via a separate provisioning container.
Current status
There are other tasks that have higher priority than Dockerization, thus I decided to stop where I was and capture my notes here to make sure they are not lost and also to get insight from others, especially prospective customers.
Below are the files I created. They are NOT finished, but they worked well enough for me to establish why I was has having difficulty with NetHSM and Cascade.
Issues encountered
- Default binding to 127.0.0.1 doesn’t work inside a container because exposing/publishing the port to the outside leaves clients unable to connect, for that to work one must listen inside the container on 0.0.0.0.
- Secrets management is a work in progress. Cascade-HSM-Bridge only works properly with NetHSM if the users AND passwords are defined in the
p11nethsm.confNetHSM PKCS#11 config file, so some way of getting external passwords, e.g. from Docker/run/secrets/xxxfiles, into thep11nethsm.conffile is needed. - Name resolution inside Docker containers seems to vary from one process to another in whether or not they can resolve the name of another container, or the value of the Docker Compose
hostnamecan be resolved, so sometimeskeyfenderas the hostname of the NetHSM container is what I need to configure and other times I needed to usenethsmwhich is the container name.
What isn’t covered here
- Persistent storage of data, e.g. via Docker volume or host mount or within the container and snapshot’ing the container.
- High availability, clustering, hot standby, etc topologies: we don’t have support for this yet in Cascade, though we think we see a way that
dnstis used we can have a single centralized key manager container (which is perhaps yet another argument for having a dedicateddnstimage) producing keys and signing instructions to be consumed by multiple running Cascade deamon instances. However, we really want to take the time to look at such architectures thoroughly and consider the edge cases and how we want to handle them. - Possibly related to the above point: replicated/shared/distributed storage among containers.
- Monitoring of the containers, e.g. what metrics might it be useful to expose via the Cascade Prometheus endpoint and are there any that are specific to containerized deployment? And is the way Cascade currently reports “health” compatible with or sufficient for dockerized deployment?
Supporting files
See the first reply to this post as including the files here makes this text too long to post!
Closing out…
So, that was a long brain dump, maybe too long for most people or anyone to actually read!
However, it would be really useful if potential operators of Cascade in a containerized setting let us know of anything they’d really like Cascade to have or they need Cascade to have in order to work better/at all in such an environment, so ladies and gentlemen, here’s your chance to let us know!
Thanks,
Ximon