0

I am trying to automate the certificate updating in Tomcat, requiring the SSL Protocol to be reloaded. This question is similar to my requirement How do I force tomcat to reload trusted certificates? and Programmatically update certificates in tomcat 8 without server restart . Essentially, I am wanting to issue the reloadSslHostConfigs jmx command. This can be accomplished via

    https://localhost/manager/jmxproxy?invoke=Catalina:type=ProtocolHandler,port=8443,address="127.0.0.1"&op=reloadSslHostConfigs

The "manager" webapp provides a handy set of actions that I can utilise, and with the "ant" definitions loaded into groovy i can quite easily reload a web. Here is a snippet of groovy to do this:

      def ant = new AntBuilder()
      ant.import (file:new File(catalina_home, "bin/catalina-tasks.xml"))
      switch (action) {
      case 'd':
        ant.deploy (url:web_base, username:username, password:pass
          , path:"/" + webs[0], update:"true", war:war_path )
        break
      case 's':
        webs.each {webapp ->
          ant.start (url:web_base, username:username, password:pass, path:"/" + webapp)
        }
        break

The documentation here https://tomcat.apache.org/tomcat-9.0-doc/manager-howto.html also shows that it supports JXMquery/get/set - but I am not sure how to apply/use it. I also see a second group of jmx and definitions that includes start/stop/invoke. I think i need the invoke type. But if or how can this be used to issue the reloadSsl command. I think I am getting closer:

ant.jmxManagerQuery (url:jmx_base, query:"Catalina:type=Environment,resourcetype=Global,name=*", username:username, password:pass)

currently returns

Trying to override old definition of datatype resources
[jmxManagerQuery] Query string is ?qry=Catalina%3Atype%3DEnvironment%2Cresourcetype%3DGlobal%2Cname%3D*
Error: : java.io.IOException: Server returned HTTP response code: 403 for URL: http://localhost:8080/manager/jmxproxy/?qry=Catalina%3Atype%3DEnvironment%2Cresourcetype%3DGlobal%2Cname%3D*

I have also enabled remote jmx, just to see if this works: https://groovy-lang.org/jmx.html#_monitoring_tomcat And indeed following these instructions I can get the documented sample working - but I would rather not enable direct jmx, but use the manager app instead. And also I cannot see how this form of jmx access would be used to provoke the ssl reload.

I see a useful example here: https://github.com/rmannibucau/letsencrypt-manager/blob/master/src/main/java/com/github/rmannibucau/letsencrypt/manager/LetsEncryptManager.java Which pulls together much of the requirements in this excellent presentation: http://tomcat.apache.org/presentations.html#latest-lets-encrypt

Its all rather confusing to an infrequent developer that just wants a simple bash or groovy script to rattle off the certificate fetch and reload without restarting Tomcat.

PS. The connector config looks like this:

    <Connector SSLEnabled="true" maxThreads="150" port="443" protocol="org.apache.coyote.http11.Http11Nio2Protocol">
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"></UpgradeProtocol>
        <SSLHostConfig certificateVerification="false" ciphers="HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!kRSA" honorCipherOrder="true" protocols="TLSv1.1,TLSv1.2">
            <Certificate certificateKeystoreFile="blaa" certificateKeystorePassword="blaa" certificateKeystoreType="JKS"></Certificate>
        </SSLHostConfig>
    </Connector>

1 Answers1

0

I now have the reloadSslHostConfigs operation working. The low down is:

  1. Tomcat's manager web app must be enabled and with a user login assigned in conf/tomcat-users.xml with the manager-jmx 'role'. (or you could enable remote JMX via the start-up parameters to support remote controlled reload).
  2. The reload does not re-read the server.xml configuration. It only re-reads the certificate file from disk. So this file (or files) must be updated with the refreshed certificate before invoking the reloadSsl.
  3. The 'bean name' might be slightly different in various environments. You could use the manager's jmx Query or Get url's (or the equivalent JMX calls) to locate the correct bean name against which the ssl certificate operation should be applied. If the bean name is not complete or incorrect, you will get something like javax.management.InstanceNotFoundException: Catalina:type=ProtocolHandler at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getMBean(DefaultMBeanServerInterceptor.java:1083)
  4. If tomcat's ssl port is not started due to a bad certificate file, then this will require correcting followed by a full Tomcat restart (or a more complex set of jmx calls). You cannot just correct the cert file and issue this ssl reload operation.

This 'groovy' code seems to work for me:

    @Grab(group='com.github.groovy-wslite', module='groovy-wslite', version='1.1.3')
    
    import wslite.rest.*
    import wslite.http.auth.*
    
    RESTClient client = new RESTClient("https://localhost/manager")
    client.httpClient.sslTrustAllCerts = true
    client.authorization = new HTTPBasicAuthorization("username", "userpassword")
    
    def path = "/jmxproxy/?invoke=Catalina:type=ProtocolHandler,port=443&op=reloadSslHostConfigs"
    def response
    response = client.get(path: path)
    println response.text

and if the configured certificate file is still valid then it returns:

OK - Operation reloadSslHostConfigs without return value

but if the certificate file now contains an invalid cert - or the file no longer exists, then you get an error response. I'm sure this can be done without the "wslite" library, or converted to Java (as groovy is a really nice Java wrapper). Or a curl or even a wget command (though these appear to return errors for me). Or any number of other things that can issue a RESTful request/response.

The manager webapp responds to localhost requests - so calling this url without localhost will respond with a 403 error. Both https or http can be used to issue these manager request url's regardless of the action being invoked.

If you like reading lots of blurb, then the above code can be adjusted with: path = "/jmxproxy/?query=*:*" and the response shows all tomcats various beans and their attributes and values.