Configure Tomcat with HTTPS/SSL on Ubuntu 16.04 LTS (Xenial) using Letsencrypt

This tutorial is different from other Tutorials as of August 2015 since it closes other ports, doesn’t use secondary web server for letsencrypt config, tomcat is configured with it’s special script, using a tomcat from its source and uses tomcat native with APR.

This tutorial is complete, from fresh server install (i.e. Ubuntu Minimal as installed on dedicated server) to the working server.

The base for this tutorial is the following:

Configure glassfish as non root user on Ubuntu: https://www.nabisoft.com/tutorials/glassfish/installing-glassfish-41-on-ubuntu

Configure letsencrypt and tomcat/jira using non-native connector: http://blog.ivantichy.cz/blogpost/view/74

Configure tomcat with APR: http://www.sheroz.com/pages/blog/installing-apache-tomcat-native-linux-ubuntu-1204.html

Set locale

sudo locale-gen "en_US.UTF-8"
sudo dpkg-reconfigure locales

Put to file /etc/environment:

LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8

Update environment

source /etc/environment

Install Java

apt-get install joe nano command-not-found python-software-properties software-properties-common
add-apt-repository ppa:webupd8team/java
apt-get update
apt-get install oracle-java8-installer
# nano /etc/environment

After opening with the text editor, we’ll need to add the following line into the bottom of the file.

JAVA_HOME="/usr/lib/jvm/java-8-oracle"

Once, the line is added, we’ll need to reload file.

source /etc/environment

check if java is working with java –version

Setup user going to run Tomcat

For legacy reasons, I call this user glassfish 😉

#Add a new user called glassfish
sudo adduser --home /home/glassfish --system --shell /bin/bash glassfish

#add a new group for glassfish administration
sudo groupadd glassfishadm

Close ports and setup port forwarding

Since Tomcat process will be running as non-root user for security concerns, let’s create

iptables.rules file with the following:

#!/bin/bash

# ATTENTION: flush/delete all existing rules
iptables -F

################################################################
# set the default policy for each of the pre-defined chains
################################################################
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

# allow establishment of connections initialised by my outgoing packets
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# accept anything on localhost
iptables -A INPUT -i lo -j ACCEPT

################################################################
#individual ports tcp
################################################################
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
iptables -A INPUT -p tcp --dport 8181 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
#uncomment next line to enable AdminGUI on port 4848:
#iptables -A INPUT -p tcp --dport 4848 -j ACCEPT

################################################################
#slow down the amount of ssh connections by the same ip address:
#wait 60 seconds if 3 times failed to connect
################################################################
iptables -I INPUT -p tcp -i eth0 --dport 22 -m state --state NEW -m recent --name sshprobe --set -j ACCEPT
iptables -I INPUT -p tcp -i eth0 --dport 22 -m state --state NEW -m recent --name sshprobe --update --seconds 60 --hitcount 3 --rttl -j DROP

#drop everything else
iptables -A INPUT -j DROP
################################################################
#Redirection Rules
################################################################
#1. redirection rules (allowing forwarding from localhost)
iptables -t nat -A OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -t nat -A OUTPUT -o lo -p tcp --dport 443 -j REDIRECT --to-port 8181

#2. redirection http
iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080

#3. redirection https
iptables -t nat -A PREROUTING -p tcp -m tcp --dport 443 -j REDIRECT --to-ports 8181
################################################################
#save the rules somewhere and make sure
#our rules get loaded if the ubuntu server is restarted
################################################################
iptables-save > /etc/my-iptables.rules
iptables-restore < /etc/my-iptables.rules
#List Rules to see what we have now
iptables -L
Make this file executable
chmod +x iptables.rules
Invoke ./iptables.rules than create File: /etc/network/if-pre-up.d/iptablesload with the content:
#!/bin/sh
/sbin/iptables-restore < /etc/my-iptables.rules
exit 0
 File: /etc/network/if-post-down.d/iptablessave
#!/bin/sh
/sbin/iptables-save -c > /etc/my-iptables.rules
if [ -f /etc/iptables.downrules ]; then
   /sbin/iptables-restore < /etc/iptables.downrules
fi
exit 0
 Set these files as executable
sudo chmod +x /etc/network/if-post-down.d/iptablessave
sudo chmod +x /etc/network/if-pre-up.d/iptablesload
You can reboot to see with iptables -L if the new config is OK

Download Tomcat from source and install it

su glassfish

As a user glassfish download Tomcat from source

cd ~
wget http://www-eu.apache.org/dist/tomcat/tomcat-8/v8.5.5/bin/apache-tomcat-8.5.5.tar.gz
tar zxvf apache-tomcat-8.5.5.tar.gz 
exit

Exit the user glassfish. Create an init script like this (as a root):

root@condor1796 ~ # cat /etc/init.d/tomcat
#! /bin/sh
### BEGIN INIT INFO
# Provides: tomcat
# Required-Start: $local_fs $network
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Tomcat
# Description: Tomcat webserver running as non root user
### END INIT INFO
#to prevent some possible problems
export AS_JAVA=/usr/lib/jvm/java-8-oracle

#GLASSFISHPATH=/home/glassfish/bin
CATALINA_HOME=/home/glassfish/apache-tomcat-8.5.5
export CATALINA_OPTS="$CATALINA_OPTS -Xmx3500m -Djdk.tls.ephemeralDHKeySize=2048"

case "$1" in
start)
echo "starting tomcat from $CATALINA_HOME"
sudo -u glassfish $CATALINA_HOME/bin/catalina.sh start
;;
restart)
$0 stop
#pause here is needed since Tomcat stop is apparently working in the background
sleep 0.1s
killall -9 java
$0 start
;;
stop)
echo "stopping tomcat from $CATALINA_HOME"
sudo -u glassfish $CATALINA_HOME/bin/catalina.sh stop
;;
*)
echo $"usage: $0 {start|stop|restart}"
exit 3
;;
esac
:

Set this script running in run levels:

sudo chmod a+x /etc/init.d/tomcat
#configure Glassfish for autostart on ubuntu boot
sudo update-rc.d tomcat defaults
#if apache2 is installed:
#stopping apache2
sudo /etc/init.d/apache2 stop
#removing apache2 from autostart
update-rc.d -f apache2 remove

Get SSL certificate using Letsencrypt

I’m following ideas from http://blog.ivantichy.cz/blogpost/view/74 but with some remarks.

Add multiverse repository to Ubuntu :

apt-add-repository multiverse && apt-get update
apt-get install letsencrypt

Run letsencrypt to generate a certificate if you already don’t have a running server

letsencrypt certonly --manual -d online-utility.org -d www.online-utility.org --agree-tos --email mladen.adamovic@gmail.com

If you have a running server, read below about ACME support servlet and script for auto renewals but don’t forget after you finished certificates to make it readable by other users since user glassfish is going to need it.

Make certificated readable by other users:

chmod o+rx /etc/letsencrypt
chmod o+rx /etc/letsencrypt/archive 
chmod o+rx /etc/letsencrypt/archive/online-utility.org
chmod o+rx /etc/letsencrypt/live

Setup Tomcat Native (APR) to enable a native connector

This part is from http://www.sheroz.com/pages/blog/installing-apache-tomcat-native-linux-ubuntu-1204.html

Install needef files

apt-get install build-essential libapr1 libapr1-dev
apt-get install make gcc openssl libssl-dev

Install APR package, from http://apr.apache.org/

wget http://www-us.apache.org/dist/apr/apr-1.5.2.tar.gz
tar zxvf apr-1.5.2.tar.gz
cd apr-1.5.2
sudo ./configure 
sudo make 
sudo make install

You should see the compiled file as

/usr/local/apr/lib/libapr-1.a

Download, compile and install Tomcat Native source package tomcat-native

wget http://www-us.apache.org/dist/tomcat/tomcat-connectors/native/1.2.8/source/tomcat-native-1.2.8-src.tar.gz
tar -xzf tomcat-native-1.2.8-src.tar.gz
cd tomcat-native-1.2.8-src/native
./configure --with-apr=/usr/local/apr --with-java-home=$JAVA_HOME
make
make install

 

Create or edit the $CATALINA_HOME/bin/setenv.sh file with following line:

export LD_LIBRARY_PATH='$LD_LIBRARY_PATH:/usr/local/apr/lib'

 

6. Restart tomcat and enjoy the desired result.

7. Configure Tomcat with APR

<Connector port="8181" protocol="org.apache.coyote.http11.Http11AprProtocol"
           SSLCertificateFile="/etc/letsencrypt/live/online-utility.org/cert.pem"
           SSLCertificateKeyFile="/etc/letsencrypt/live/online-utility.org/privkey.pem"
           SSLCertificateChainFile="/etc/letsencrypt/live/online-utility.org/chain.pem"
           SSLVerifyClient="optional" SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"
           connectionTimeout="20000" acceptCount="30000"
           acceptorThreadCount="2" 
           compression="on" maxConnections="50000" maxThreads="400"
           compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,image/svg+xml,image/svg" 
           useSendfile="false"
           maxHttpHeaderSize="16392" SSLEnabled="true"
           enableLookups="false" 
           scheme="https" secure="true"   clientAuth="false"  useBodyEncodingForURI="true"  
/>

Enable HSTS in Tomcat

uncomment and edit lines in ~glassfish/apache-tomcat/conf/web.xml:

<filter>
        <filter-name>httpHeaderSecurity</filter-name>
        <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
        <init-param>
           <param-name>hstsMaxAgeSeconds</param-name>
           <param-value>31536000</param-value>
        </init-param>
        <async-supported>true</async-supported>
    </filter>

<filter-mapping>
        <filter-name>httpHeaderSecurity</filter-name>
        <url-pattern>/*</url-pattern>
        <url-pattern>*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
</filter-mapping>

 

 

Configure HTTP redirect application with support to ACME challenge

I’m using a web app with the one server which forwards HTTP to HTTPS but accepts ACME challenge:

package redirect;

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>

}

 

It’s important to have Tomcat configured with this application (I call it appBaseRedirectToHttpsWithAcme) on port 8080 (since port 80 is forwarded to port 8080) in our iptables configuration:

<Service name="CatalinaHttp">
   <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000" acceptCount="30000"
               acceptorThreadCount="2" 
               compression="on" maxConnections="50000" maxThreads="100"
               compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,image/svg+xml,image/svg" 
               useSendfile="false" />
   <Engine name="CatalinaHttp" defaultHost="localhost">
      <Host name="www.online-utility.org"  appBase="appBaseRedirectToHttpsWithAcme"  unpackWARs="true" autoDeploy="true">
            <Alias>www.online-utility.org</Alias>
            <Alias>new.online-utility.org</Alias>
            <Alias>online-utility.org</Alias>
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="http_www_online_utility_org_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
    </Service>


Setup a script for letsencrypt auto renewals

Lets call this script renew_cert.sh

#!/bin/bash
letsencrypt 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@

/etc/init.d/tomcat restart

Don’t forget to make it executable:

chmod +x renew_cert.sh

Ensure this script is working as intended:

./renew_cert.sh

When it is working, it is time to put it into a cron job:

crontab -e

add the line to run it each month:

# m h  dom mon dow   command
0 10 1 * * /root/renew_cert.sh

Optional addons

Don’t forget other Linux stuff you might want like

apt-get install smartmontools
apt-get install postfix

If you are going to send email from Tomcat, don’t forget to add javax.mail.jar to /lib

If you are going to have a large number of connections per second, don’t forget to add to /etc/security/limits.conf

* soft nofile 1000000
* hard nofile 1000000

 

Congratulations

Everything should be setup now. For help, I’d suggest ask on askubuntu.com and Tomcat mailing list (depending of the actual) problem.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s