Я пытаюсь заставить 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