Leverage confd to eliminate hardcoded passwords from your tomcat docker image

Recently I dockerized some JavaEE-apps which run within an Apache Tomcat appserver. I needed a tool for provisioning different configuration files in a dynamic manner — many of those files have environment specific fields which should not be coded statically inside some container layer. According to the 12factor-principles they should be set through granular environment variables.
The tool which perfectly fit my needs turned out to be confd by Kelsey Hightower.
As an example I will show in this post how you can
- Configure the Tomcat manager app with a password provided or generated at container start.
- Use confd to render the tomcat-users.xml configuration file.
- Define a
HEALTHCHECKcommand which accesses the tomcat-manager-app using the dynamic password.
My first requirement: I wanted the tomcat’s manager-interface to be secure-by-default and zero-configuration at the same time.
$ docker run confd-tomcat should not fail complaining on a missing password but just work out of the box. One advantage of docker is that it makes running applications so easy — let’s stick to that!
A custom script launched at container start takes care of it. It checks if a password has been provided as an environment variable and — if not — generates a new one:
The script first ensures that a TOMCAT_MANAGER_PASSWORD is available. Either set from outside or by generating one. Note that I use the non-blocking urandom-device for getting some random bytes. The security of that approach is at least debatable within container-environments.
Then confd is executed to render the tomcat configuration files accordingly. Finally tomcat’s usual catalina.sh start-script is launched.
If the container is launched without a password the output is as follows:
$ docker run confd-tomcat
TOMCAT_MANAGER_PASSWORD not provided. Generated: 61220e1175281eAlternatively the password can be set from outside:
$ docker run -e TOMCAT_MANAGER_PASSWORD=abc123 confd-tomcatwhich will adopt the password silently.
The next step consists of configuring confd to generate the tomcat-users.xml file correctly. Kelsey provides in-depth-documentation on this part. Two files are needed: 1. A template of tomcat-users.xml with a placeholder for the password and 2. a configuration file which describes the rendering-task.
Compared to the default tomcat-user.xml file shipped with a factory fresh tomcat you’ll note the embedded Golang-placeholder {{…}} where we find some hardcoded password usually.
confd automatically maps the environment variable TOMCAT_MANAGER_PASSWORD to the key /tomcat/manager/password when the run.sh script executes confd -onetime -backend env . Subsequently it writes the file with the correct password to the specified destination.
The final Dockerfile looks like this:
The HEALTHCHECK directive is worth a look here. First of all the environment variable needs to be escaped such that it is not evaluated at image-build-time and replaced by the empty String defined in line 5. The variable should be expanded later at container runtime to take the correct value.
A second caveat here is that HEALTHCHECK commands are executed from within the docker container. This is essential to be able to use the correct password even when it has been generated automatically. In addition to that the manager-interface of tomcat is only accessible from localhost by default. Trying to access the manager-app from the docker-host will fail because host and container do not share the same network.
However, nothing prevents exposing the manager-app for remote-access now, as the tomcat-password is not longer hard-coded to some insecure dummy-password.
You can find the code used in this blog post here: https://github.com/fabian-braun/tomcat-confd
