Migration to ACMEv2: Help in Fixing Tutorial on Letsencrypt with Tomcat

Hi everyone,

four years ago I made a way to configure Tomcat/Tomcat Native/Letsencrypt.

I’ve used that configuration for several severs and documented it publicly as this tutorial: https://mladenadamovic.wordpress.com/2016/09/06/configure-tomcat-with-ssl-on-ubuntu-minimal/

However, recently I started to receive the email that I should upgrade to ACMEv2.

I’ve installed Certbot from https://certbot.eff.org/lets-encrypt/ubuntuxenial-other
and updated my script to use certbot .

I thought that I should migrate my RedirectToHttpsWithAcme class (as described in the tutorial) to ACMEv2
I was reading documentation of ACME4J at https://shredzone.org/maven/acme4j/index.html
But it looked very complicated!

And I decided to try dry-run what happens when I run certbot:
certbot --dry-run certonly --webroot --webroot-path /tmp/letsencrypt/public_html -d online-utility.org -d www.online-utility.org -d ww2.online-utility.org -d new.online-utility.org --agree-tos --email mladen.adamovic@gmail.com

to my surprise, I didn’t see any error message and it looks that it now uses ACMEv2, as I’ve seen in the logs:
https://acme-staging-v02.api.letsencrypt.org:443 "POST /acme/finalize/313560/73238149 HTTP/1.1" 200 905

Is this right? This from the log suggest that it’s using ACMEv2?

If that is correct it seems that I can still with ACMEv2 continue to use RedirectToHttpsWithAcme class as described in my tutorial:

(for the reference, this is that class):

import commons.FilesOperations;
import commons.UsualHtmlUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author mladen
 */
@WebServlet(name = "RedirectToHttpsWithAcme", urlPatterns = {"/*", "/"})
public class RedirectToHttpsWithAcme extends HttpServlet {

  /**
   * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String requestUrl = request.getRequestURL().toString();
    if (requestUrl.contains(".well-known/acme-challenge/")) {
      int indexFilename = requestUrl.lastIndexOf("/") + 1;
      boolean wasError = true;
      if (indexFilename > 0 && indexFilename < requestUrl.length()) {
        String filename = requestUrl.substring(indexFilename);
        File existingFile = new File("/tmp/letsencrypt/public_html/.well-known/acme-challenge/" +  filename);
        if (existingFile.exists()) {
          response.setContentType("text/plain");
          OutputStream out = response.getOutputStream();
          FileInputStream in = new FileInputStream(existingFile);
          FilesOperations.inputStreamToOutputStream(in, out);
          wasError = false;
        }
      }
      if (wasError) {
        throw new ServletException("invalid requestUrl " + requestUrl);
      }
    } else {
      response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
      int indexOfSlash = requestUrl.indexOf("//");
      if (indexOfSlash > 0) {
        String redirectUrl = "https:" + requestUrl.substring(indexOfSlash);
        String queryString = request.getQueryString();
        if (queryString != null && queryString.length() > 0) {
          redirectUrl += "?" + UsualHtmlUtils.encodeURL(queryString);
        }
        response.setHeader("Location", redirectUrl);
      } else {
        throw new ServletException("invalid requestUrl " + requestUrl);
      }

    }
  }

  // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
  /**
   * Handles the HTTP <code>GET</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    processRequest(request, response);
  }

  /**
   * Handles the HTTP <code>POST</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    processRequest(request, response);
  }

  /**
   * Returns a short description of the servlet.
   *
   * @return a String containing servlet description
   */
  @Override
  public String getServletInfo() {
    return "Short description";
  }// </editor-fold>

}
1 Like

I haven’t read your blog post closely, but as a short reply:

The ACME client “letsencrypt” was renamed to “Certbot” in 2016. (Not long after Ubuntu 16.04 was released.)

It’s added new features and improvements, of course, but it remains more or less backwards compatible with earlier versions. The executable was renamed from “letsencrypt” to “certbot”, but the older command line arguments almost all still exist – and many packages still install identical “letsencrypt” and “certbot” executables for backwards compatibility.

HTTP validation has not changed.

Newer versions of Certbot automatically use the Let’s Encrypt’s ACMEv2 APIs without users having to do anything beyond upgrading the software.

For most users, they can upgrade from an old version to a new version, and whatever they were doing before will continue to work.

Some best practices may have changed, though.

I suspect what you’re doing will mostly work, but I think it will not completely work. (Which is a critical difference!)

You followed the instructions to install it using the ppa:certbot/certbot PPA?

1 Like

You followed the instructions to install it using the ppa:certbot/certbot PPA?

Yes

1 Like

One matter is that newer Certbot packages automatically set up a systemd timer (or a cron job if you’re on an ancient version of Ubuntu) that runs “certbot renew” twice a day.

That somewhat conflicts with your custom cron job – though, on second thought, neither one should break the other.

I would suggest replacing your cron job with a deploy hook – you can put a shell script in /etc/letsencrypt/renewal-hooks/deploy/ that just runs /etc/init.d/tomcat restart and then delete your cron job. That way the package’s timer will automatically renew your certificate every 60 days (based on the saved configuration information in /etc/letsencrypt/renewal/) and restart Tomcat afterwards.


An issue you will run into now or in the future is permissions on the files and directories in /etc/letsencrypt/. Your tutorial makes several directories world-readable, but that isn’t or won’t be enough.

(I’m not sure which version of Certbot you’re running, and I don’t remember exactly when various changes were made, and it’s possible some of them haven’t been made yet at all. So you might not run into permission issues now but you will eventually.)

Assuming you have a multi-user system, it’s a security problem to make your private key files world-readable.

Older versions of Certbot relied on making the directories not-world-readable, and your tutorial changes the permissions so that Tomcat (and everything else) can read everything.

Recent versions of Certbot make the private key files non-world-readable, so your current design will fail.

Recent or future versions of Certbot may also automatically chmod the directories again, undoing your change.

The most recommended way to handle this is to have your new deploy hook copy the certificate and key files somewhere else, with permissions that Tomcat can work with, and then have Tomcat use the copies.


Off the top of my head, I don’t think you’ll run into other issues. But I’m not a Tomcat expert and I still haven’t read your post extremely closely. But from my knowledge of what’s changed in Certbot, I don’t think you’ll run into other new issues while upgrading.

1 Like

Hi Matt,

thanks for your help. I did test it and apparently new certbot when creating new certificates, doesn’t set file permissions so that other users cannot read or execute the directory.

Also, certbot renew works as intended so it doesn’t interfere with my cron job.

So I’m concluding that the removal of the old letsencrypt package and install a new certbot in my tutorial would be sufficient (at least for now).

1 Like