|
Notes on setting up TLS
|
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.
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.")
Add this to configure Tomcat via server.xml to offer SSL/TLS via a
second connector, without disturbing the unsecure HTTP 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.)
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.
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:
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:
port="8443" protocol="HTTP/1.1"
connectionTimeout="20000"
scheme="https"
secure="true"
SSLEnabled="true"
certificateKeyAlias="tomcat"
certificateKeystoreFile="conf/selfsigned.jks"
certificateKeystorePassword="changeit"
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.
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:
.
.
.
<!-- shipping configuration for HTTP: -->
.
.
.
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
<!-- support for TLS (HTTPS): -->
port="8443" protocol="HTTP/1.1"
connectionTimeout="20000"
scheme="https"
secure="true"
SSLEnabled="true"
certificateKeyAlias="tomcat"
certificateKeystoreFile="/certificates/selfsigned.jks"
certificateKeystorePassword="changeit"
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 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.
...to enable TLS in Tomcat and provide for consumer-supplied certificates one must:
* We've been calling it /certificates here, but anything will do.
Optional polish...
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