Назад | Перейти на главную страницу

Заставляем JMX работать под Tomcat 7 с SSL и самозаверяющим сертификатом

Я пытаюсь заставить JMX работать под Tomcat 7.0.23 с SSL. Серверы расположены в AWS, что означает, что все хосты подключены к NAT, и мне нужно использовать JmxRemoteLifecycleListener, чтобы явно установить два порта, используемые JMX. Я много читал по этой теме, но просто не могу собрать все части вместе должным образом.

Я могу заставить JMX нормально работать без SSL. Я загрузил версию catalina-jmx-remote.jar для моей версии Tomcat и установил ее в свой каталог tomcat / lib. Мой server.xml содержит:

  <Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener" 
        rmiRegistryPortPlatform="1099" rmiServerPortPlatform="1098" />

Когда я запускаю Tomcat со следующими настройками, я могу подключиться к незащищенному сеансу:

-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password 
-Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access 
-Djava.rmi.server.hostname=<public IP of server> 
-Dcom.sun.management.jmxremote.ssl=false

Однако, если я изменю их на следующие, я не смогу установить соединение SSL:

-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password 
-Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access 
-Djava.rmi.server.hostname=<public IP of server> 
-Dcom.sun.management.jmxremote.ssl=true
-Dcom.sun.management.jmxremote.registry.ssl=true
-Dcom.sun.management.jmxremote.ssl.need.client.auth=false 
-Dcom.sun.management.jmxremote.authenticate=true
-Djavax.net.ssl.keyStore=/path/to/keystore.dat 
-Djavax.net.ssl.keyStorePassword=<password>
-Djavax.net.ssl.trustStore=/path/to/truststore.dat 
-Djavax.net.ssl.trustStorePassword=<password>

keystore.dat содержит только один сертификат, созданный с помощью:

openssl x509 -outform der -in cert.pem -out cert.der
keytool -import -alias tomcat -keystore keystore.dat -file cert.der -storepass <password>

truststore.dat содержит полную копию java cacerts плюс сертификат CA для моего самозаверяющего сертификата:

cp $JAVA_HOME/jre/lib/security/cacerts truststore.dat
keytool -storepasswd -storepass changeit -new <password> -keystore truststore.dat
keytool -import -trustcacerts -file mycacert.pem -alias myalias -keystore truststore.dat -storepass <password>

После запуска Tomcat я попытался подключиться через jconsole, но не смог установить соединение. Я пытался проверить SSL с помощью openssl, но похоже, что Tomcat не использует сертификат:

$ openssl s_client -connect <host>:1099
CONNECTED(00000003)
140735160957372:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 322 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---

Я проверил, что мое локальное хранилище ключей и хранилище доверенных сертификатов настроены правильно, экспортировав ключи и проверив цепочку сертификатов (Combined.pem - это все сертификаты CA из truststore.dat, а cert.pem - это мой сертификат из keystore.dat):

$ openssl verify -verbose -purpose sslserver -CAfile combined.pem cert.pem
cert.pem: OK

Так что теперь я в полной растерянности. Сертификат и сертификат CA выглядят правильно. Незашифрованные соединения JMX работают. Но я не могу получить соединение для использования SSL. Что мне здесь не хватает?

Я не знаю, просто отвлекающий маневр или нет, но я не вижу способа указать, какой сертификат в хранилище ключей используется JMX. Кое-что из того, что я прочитал, подразумевает, что он просто использует сертификат с псевдонимом «tomcat». Это правильно?

Мне потребовалось время, чтобы понять это, но в конце концов я понял. Ключ в том, как настраиваются файлы хранилища ключей и доверенных сертификатов при создании самозаверяющих сертификатов. Вот сценарий bash, который я написал, чтобы его было легко запомнить:

DNAME="CN=foo JMX, OU=prodops, O=foo.com, L=Somewhere, S=XX, C=US"
DAYS=3650
PASSWORD=<password>
CACERTS="/path/to/java/jre/lib/security/cacerts"

rm -f jconsole* tomcat*

# First, create the keystore and truststore for the application, tomcat in this case.  Use $CACERTS as the basis for the new keystore & truststore so that all public CA's remain intact:

keytool -genkey -alias tomcat -keyalg RSA -validity ${DAYS} -keystore tomcat.keystore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"
cp ${CACERTS} tomcat.truststore
keytool -storepasswd -keystore tomcat.truststore -storepass changeit -new ${PASSWORD}
keytool -genkey -alias tomcat -keyalg RSA -validity ${DAYS} -keystore tomcat.truststore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"

# And do the same for the JMX client, jconsole in this case:

keytool -genkey -alias jconsole -keyalg RSA -validity ${DAYS} -keystore jconsole.keystore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"
cp ${CACERTS} jconsole.truststore
keytool -storepasswd -keystore jconsole.truststore -storepass changeit -new ${PASSWORD}
keytool -genkey -alias jconsole -keyalg RSA -validity ${DAYS} -keystore jconsole.truststore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"

# Then, export the public certificates from the keystores:

keytool -export -alias tomcat -keystore tomcat.keystore -file tomcat.cer -storepass ${PASSWORD}
keytool -export -alias jconsole -keystore jconsole.keystore -file jconsole.cer -storepass ${PASSWORD}

# Finally, import the certificates into the truststores. Again, this allows the application (tomcat) to trust the client (jconsole), and vice-versa:

keytool -import -alias jconsole -file jconsole.cer -keystore tomcat.truststore -storepass ${PASSWORD} -noprompt
keytool -import -alias tomcat -file tomcat.cer -keystore jconsole.truststore -storepass ${PASSWORD} -noprompt

rm -f *.cer