Notes on setting up TLS
    in Tomcat and Docker

Russell Bateman
July 2022
last update:

Table of contents

Tomcat 9's shipping server.xml

Tomcat ships server.xml with only the following connector awakened. To remove the possibility of reaching Tomcat over unsecured port 8080, simply erase this (or comment it out). Leaving it in simply means HTTP will continue to work alongside HTTPS; there's no "interference" between the two.


<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

(redirectPort="8443" simply means, "if you hit me with https://hostname:8080/..., I'm going to redirect your request to the configured connector that handles TLS. If none is configured, too bad for you.")

Insert TLS configuration into Tomcat 9's shipping server.xml

Add this to configure Tomcat via server.xml to offer SSL/TLS via a second connector, without disturbing the unsecure HTTP connector.


<Connector port="8443" protocol="HTTP/1.1"
           connectionTimeout="20000"
           other configuration as you may have set
           scheme="https"
           secure="true"
           SSLEnabled="true"
           keyAlias="tomcat"
           keystoreFile="conf/tomcat.jks"
           keystorePass="changeit"
           clientAuth="false" />

This says that we're copying the keystore into ${CATALINA_BASE}/conf, which is not what we want to do. Instead, we'll later change this to a "Docker" path—to some external filesystem mounted enabling the MDIX subscriber to specify, on the docker run command line, what that filesystem path to his keystore is.

(Note that clientAuth says whether or not there's a client-log-in enacted, which is unnecessary in our case.)

How (officially) to get a certificate from an authority

See Installing a Certificate from a Certificate Authority, however, for internal use, there's little reason to pay good money for a formal, commercial certificate.

How to generate a self-signed certificate

You don't have to submit a CSR to a commercial certificate authority (CA) if you don't need such an authority chain. Why? Because you're doing something internal and don't care about a formal, commercial authority chain.

(Note: I happen to be following the second answer at How to create a self-signed SSL certificate for use with Tomcat.)

Generate the SSL certificate. All the interactive bits below could be on the command line too, but it might be instructive to leave them like this:

root@nargothrond:/opt/tomcat/conf# which keytool    (this is more or less Java's version of the openssl tool)
/usr/bin/keytool
root@nargothrond:/opt/tomcat/conf# keytool -genkeypair              \
                                           -keyalg   RSA            \
                                           -alias    tomcat         \
                                           -keystore selfsigned.jks \
                                           -validity 365            \
                                           -keysize  2048
Enter keystore password: changeit
Re-enter new password: changeit
What is your first and last name?
  [Unknown]:  windofkeltia.com
What is the name of your organizational unit?
  [Unknown]: 
What is the name of your organization?
  [Unknown]:  Wind of Keltia
What is the name of your City or Locality?
  [Unknown]:  Provo
What is the name of your State or Province?
  [Unknown]:  UT
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=windofkeltia.com, OU=Unknown, O=Wind of Keltia, L=Provo, ST=UT, C=US correct?
  [no]:  yes

Using an alias of "tomcat" isn't strictly required. Aliases help sort between certificates in stores when you have myriad different ones. If you use an alias, keep the case identical—don't trust that "Tomcat" will or will not be the same as "tomcat" (unless your experimentation reassures you they are).

Verify the SSL certificate just generated.

root@nargothrond:/opt/tomcat/conf# ll selfsigned.jks
-rw-r--r-- 1 root root 2729 Jul 19 13:22 selfsigned.jks
root@nargothrond:/opt/tomcat/conf# keytool -list -v -keystore selfsigned.jks
Enter keystore password: changeit
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: tomcat
Creation date: Jul 19, 2022
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=windofkeltia.com, OU=Unknown, O=Wind of Keltia, L=Provo, ST=UT, C=US
Issuer: CN=windofkeltia.com, OU=Unknown, O=Wind of Keltia, L=Provo, ST=UT, C=US
Serial number: 313cad91
Valid from: Tue Jul 19 13:22:01 MDT 2022 until: Wed Jul 19 13:22:01 MDT 2023
Certificate fingerprints:
	 SHA1: 89:E5:A6:B9:58:B2:00:FA:08:17:C3:E0:89:D8:40:8A:46:94:51:DE
	 SHA256: 29:B4:8B:29:84:B3:9A:47:35:5A:7D:6A:97:20:E4:A8:99:11:97:98:BA:AD:05:9B:86:B4:10:BD:FD:4E:BD:CE
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: AC F2 DB 75 0C A8 9E 36   DA 38 D5 E6 00 6E 74 F8  ...u...6.8...nt.
0010: 8D 04 95 D2                                        ....
]
]

*******************************************
*******************************************

Export the certificate from the keystore to a separate certificate file.

root@nargothrond:/opt/tomcat/conf# keytool -export                   \
                                           -alias     tomcat         \
                                           -file      selfsigned.crt \
                                           -keystore  selfsigned.jks \
                                           -storepass changeit
Certificate stored in file <selfsigned.crt>

Verify the contents of the certificate file.

root@nargothrond:/opt/tomcat/conf# ll selfsigned.crt
-rw-r--r-- 1 root root 899 Jul 19 13:34 selfsigned.crt
root@nargothrond:/opt/tomcat/conf# keytool -printcert -v -file selfsigned.crt
Owner: CN=windofkeltia.com, OU=Unknown, O=Wind of Keltia, L=Provo, ST=UT, C=US
Issuer: CN=windofkeltia.com, OU=Unknown, O=Wind of Keltia, L=Provo, ST=UT, C=US
Serial number: 313cad91
Valid from: Tue Jul 19 13:22:01 MDT 2022 until: Wed Jul 19 13:22:01 MDT 2023
Certificate fingerprints:
	 SHA1: 89:E5:A6:B9:58:B2:00:FA:08:17:C3:E0:89:D8:40:8A:46:94:51:DE
	 SHA256: 29:B4:8B:29:84:B3:9A:47:35:5A:7D:6A:97:20:E4:A8:99:11:97:98:BA:AD:05:9B:86:B4:10:BD:FD:4E:BD:CE
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: AC F2 DB 75 0C A8 9E 36   DA 38 D5 E6 00 6E 74 F8  ...u...6.8...nt.
0010: 8D 04 95 D2                                        ....
]
]

Import the certificate into the trust store.

root@nargothrond:/opt/tomcat/conf# keytool -import -noprompt -trustcacerts \
                                           -alias     tomcat               \
                                           -file      selfsigned.crt       \
                                           -keystore  selfsigned.ts        \
                                           -storepass changeit
Certificate was added to keystore

Verify that the trust store was created.

root@nargothrond:/opt/tomcat/conf# ll selfsigned.ts
-rw-r--r-- 1 root root 1255 Jul 19 13:41 selfsigned.ts

The certificate is complete, ensconced in the trust store and may now be used by the Apache Tomcat server, with a connector element in server.xml configured thus:


<Connector port="8443" protocol="HTTP/1.1"
           connectionTimeout="20000"
           scheme="https"
           secure="true"
           SSLEnabled="true"
           keyAlias="tomcat"
           keystoreFile="conf/selfsigned.jks"
           keystorePass="changeit"
           clientAuth="false" />

Despite that selfsigned.crt is inside selfsigned.ts, the trust store, Tomcat configuration asks for

...
keystoreFile="conf/selfsigned.jks"
...

and not (the non-existent)

...
truststoreFile="conf/selfsigned.ts"
...

We also want to try out this configuration, purportedly de rigueur since Tomcat 8. The attributes we have been using belonged to NIO and NIO2 SSL. I found that both these configurations worked for Tomcat 9. Obviously, this one is more correct because not deprecated:


<Connector port="8443" protocol="HTTP/1.1"
           connectionTimeout="20000"
           scheme="https"
           secure="true"
           SSLEnabled="true">
  <SSLHostConfig>
    <Certificate
           certificateKeyAlias="tomcat"
           certificateKeystoreFile="conf/selfsigned.jks"
           certificateKeystorePassword="changeit" />
  </SSLHostConfig>
</Connector>

Testing Tomcat via TLS...

To test, we bounce Tomcat and go to the browser. (It is necessary to avoid leaving server.xml in any editor during this process.) What this will show, for any normal/simple Tomcat installation, is the Tomcat Manager splash page. For embedded Tomcat, your mileage may vary.

# systemctl restart tomcat
http://localhost:8080/     That's right: HTTP still works!*
https://localhost:8443/    and, if the planets are aligned, HTTPS now works!

* To disallow non-TLS access, remove the connector configuration for port 8080 as noted at the beginning of this page.

What to do for Docker?

Ideally, we'd like to set things up such that either of these attributes from the configuration above which was done in ${CATALINA_BASE}/conf can get their credentials from the filesystem.

However, for Docker and Tomcat inside of Docker, making use of ${CATALINA_BASE}/conf is inappropriate, difficult or even impossible such as when embedded inside an application running in the Docker container.

Instead, let's imagine configuring server.xml more naively to look for certificates on a path that may not even exist. Like this:

    keystoreFile="/certificates/selfsigned.jks"
    or
    certificateKeystoreFile="/certificates/selfsigned.jks"

Remember, you're free to use either the older configuration with keystoreFile and keystorePass, but, for brevity's sake, we'll use only the more modern (post Tomcat 8) one from now on.

Not only this, but let's leave the HTTP configuration in place.

Here's just the Connector configuration now:

server.xml:


.
.
.
<!-- shipping configuration for HTTP: -->
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

<!-- support for TLS (HTTPS): -->
<Connector port="8443" protocol="HTTP/1.1"
           connectionTimeout="20000"
           scheme="https"
           secure="true"
           SSLEnabled="true">
  <SSLHostConfig>
    <Certificate
           certificateKeyAlias="tomcat"
           certificateKeystoreFile="/certificates/selfsigned.jks"
           certificateKeystorePassword="changeit" />
  </SSLHostConfig>
</Connector>
.
.
.

We moved the certificates from Tomcat's configuration subdirectory to /certificates. What happens?

This TLS configuration will fail (if quietly) unless there's a certificates subdirectory off the root. It's an idiotic idea to imagine creating such a subdirectory—unless this happens to be installed in a Docker container.

Ah!

Now, the external consumer's certificate(s) to be used should reside on the filesystem of the Docker container's host. How can we make this happen?

It's all in the docker run command the consumer issues. To make what's above work only requires the addition of the --volume command. First, here's how the Docker command might appear when TLS doesn't interest us (i.e.: just using HTTP):

$ docker run --publish 5000:8080 service:latest

And, here's how we add a subdirectory off the root inside the container—mapping it to where in the host those keys and certificates live:

$ docker run --publish 5000:8080 --volume path:/certificates service:latest

...where path is wherever the certificates live on the host. We don't suggest what that might be except that it would be a subdirectory on the host containing selfsigned.jks—the sample credential we used to illustrate Tomcat/TLS examples earlier in this document. By way of example, though, let's say

--volume /home/russ/certs:/certificates

...meaning that the Docker container will run with a /certificates subdirectory at its root and that subdirectory will be identical to path /home/russ/certs in the host's filesystem.

Note that Dockerfile isn't used to create a certificates subdirectory in the container. There is no need.

The password and the alias

The last problems to deal with are the password—"changeit"—in our example and the alias, "tomcat."

There is no way around the configuration of server.xml (and hence the owner of the Tomcat application) being required to prescribe the password used to create the keys and certificates.

Similarly, the party that configures server.xml must also impose the alias to be used.

In summary, setting up Tomcat on TLS in Docker...

...to enable TLS in Tomcat and provide for consumer-supplied certificates one must:

  1. Configure TLS in a <Connector port="8443" ... /> in ${CATALINA_BASE}/conf/server.xml.
  2. Use and publish the name of a nicely named subdirectory* for the certificate store files.
  3. Communicate to consumer
    1. the name of the certificate file expected,
    2. its type,
    3. the alias to associate with it and
    4. password to be used in creating it.
  4. Instruct consumer as to
    1. how to use Docker's --volume mapping command
    2. to map the host computer's subdirectory containing certifications
    3. to the nicely named subdirectory in the Docker container.

* We've been calling it /certificates here, but anything will do.

Extra credit or alternative exercises...

Optional polish...

  1. Deny the use of HTTP with Tomcat by removing the <Connector port="8080" ... /> configuration.
  2. Restore unauthenticated yet encrypted use of Tomcat for consumers via the configuration of
    1. <Connector port="8443" ... />,
    2. Dockerfile (using command VOLUME) of a nicely named subdirectory for certificates, and
    3. the creation of generic, self-signed certificates that will live in that subdirectory in the Docker container.

I have a service running in Tomcat which I reach over TLS because configured with a <Connector port="8443" ... /> thus:

    <Connector port="8443" protocol="HTTP/1.1"
               connectionTimeout="20000"
               scheme="https"
               secure="true"
               SSLEnabled="true">
      <SSLHostConfig>
        <Certificate
          certificateKeyAlias="tomcat"
          certificateKeystoreFile="conf/tomcat.jks"
          certificateKeystorePassword="changeit" />
      </SSLHostConfig>
    </Connector>

...and a self-signed certificate which, for now, sits in ${CATALINA_BASE}/conf as can be seen by the configuration above.

tomcat.jks looks like this:

# keytool -list -v -keystore tomcat.jks -storepass changeit
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: tomcat
Creation date: Jul 12, 2022
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=windofkeltia.com, OU=Unknown, O=Wind of Keltia, L=Provo, ST=UT, C=US
Issuer: CN=windofkeltia.com, OU=Unknown, O=Wind of Keltia, L=Provo, ST=UT, C=US
Serial number: 59d83225
Valid from: Tue Jul 12 10:23:17 MDT 2022 until: Mon Oct 10 10:23:17 MDT 2022
Certificate fingerprints:
	 SHA1: 9B:CE:D7:71:63:0D:1D:05:EE:00:11:39:F3:86:68:42:86:72:E2:62
	 SHA256: D3:7A:D0:EE:DC:9A:C0:FD:49:3B:B3:4D:7D:F7:BC:56:B3:22:EA:44:CD:BA:90:56:91:98:CD:C3:19:B9:D6:76
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 33 8E 2E B5 C9 E8 42 AE   11 33 F2 7A 1E 92 70 81  3.....B..3.z..p.
0010: DF 83 51 33                                        ..Q3
]
]

*******************************************
*******************************************

This works.

I am trying to reach this service from NiFi, via InvokeHTTP with this configuration:

        HTTP Method: POST
         Remove URL: https://localhost:8443/service
SSL Context Service: Standard[Restricted]SSLContextService configured:
  Keystore Filename: (various places)/tomcat.jks
  Keystore Password: changeit
       Key Password: changeit
      Keystore Type: PKCS12
Truststore Filename:
Truststore Password: changeit
    Truststore Type: PKCS12
       TLS Protocol: TLS