Remote Part: tomcat-with-ssl

tomcat-with-ssl

Tomcat-with-ssl is a remote part that you can use in your own snap to deploy an Apache Tomcat server, your web app and an SSL certificate.

This part takes what is perhaps an unusual path, in that it has Tomcat expose port 443 directly rather than requiring a web server such as Apache.

The part is designed to facilitate deploying a SINGLE webapp under Tomcat.

The end result is a secure web server that takes two lines to install and configure:

sudo snap install yourwebapp sudo yourwebapp.getcert <youremail> <fqdn>

You can start stop tomcat via: snap start yourwebapp.tomcat snap stop yourwebapp.tomcat

As of release the app supports tomcat 8.5 but you can easily modify the version.

Lets Encrypt

The part uses ‘certbot’ from Lets Encrypt to obtain a certificate and renew that certificate as required.

One current weakness with this implementation is that it checks for an expiring certificate every 15 minutes. When your certificate needs to be renewed, it will renew it immediately AND RESTART Tomcat. This is likely to be problematic for a serious webapp as random restarts are usually not welcome. I will look at improving this in a later release.

Using tomcat-with-ssl in your snap.

To include tomcat-with-ssl in your snap you need to define the app entry points as well as including the part:

As your are deploying a webapp you don’t need your own ‘app’ entry in your snapcraft.yaml. Instead you use the following apps:

  apps:
    tomcat:
      command: tomcat-launch
      daemon: simple
      plugs: [network, network-bind]

      # used to ran the certbot renewal process.
      cron:
        command: cron
        daemon: simple
        plugs: [network, network-bind]

      # Used to obtain a certificate post install
      getcert:
        command: getcert
        plugs: [network, network-bind]

and you then need to define the parts and copy your webapp to the correct location:

  parts:
    # build the web app
    your-webapp:
      plugin: maven
      source: git@github.org:yourusername/yourwebapp.git
      maven-options:
        [-DskipTests=true]
      organize:
        war/yourwebapp-1.0-SNAPSHOT.war : webapps/ROOT.war
      after: [setup-repos,getcert,tomcat-with-ssl]

The key pieces in the above part are:

plugin

Defines what build tool you use for your webapp. This example uses maven to do the build. You need to use the snapcraft plugin that matches your build environment.

See the list of snapcraft plugins here: Snapcraft Plugins

source

The path to where you code lives. This could be on the local filesystem or as in the example the code is pulled from github.

maven-options

The is specific to the maven plugin. If you don’t have any unit tests or are using another plugin then you don’t need this line.

organise

This is critical as its job is to take the ‘war’ file generated from your build and copy it into the tomcat webapps directory.

Recommended In the example we rename your web app war to ROOT.war which makes the web app accessible from https://<fqdn>

If you don’t rename your war to ROOT.war then your website will be accessible from: https://<fqdn>/<yourwarname>

after

The after statement is critical and it causes snapcraft to pull in the tomcat-with-ssl remote part. Note: there is currently a bug in snapcraft that requires the after statement to list all three parts that tomcat-with-ssl is composed of. Once the bug is fixed the after statement will ONLY require ‘tomcat-with-ssl’.

Force all traffic via HTTPS

You should be forcing all traffic to your website via https. To do this you need to alter your web.xml file to add a security constraint. Note: this needs to be contained in your war file, you CAN’T make these changes directly on the installed tomcat instance.

web.xml

In your web.xml file in the <web-app *> section add:

<security-constraint>
    <web-resource-collection>
         <web-resource-name>Protected Context</web-resource-name>
         <url-pattern>/*</url-pattern>
   </web-resource-collection>
   <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

Annotations

If you use annotations rather than web.xml you can also achieve the same result by creating a filter that redirects all port 80 traffic to port 443.

  • if you know how to specify the ‘transport-guarentee’ directly with annotations add a reply to this post or update the doco :slight_smile:
package au.org.noojee.orionmonitor.servlets;

import java.io.IOException;
import java.net.URL;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;

/**
 * This filter redirects all traffic to https (port 443).
 */

// Wire this filter in.
@WebFilter(filterName = "HTTPSRedirect", urlPatterns = { "/*" }, asyncSupported = true)

public class HTTPSRedirectFilter implements Filter
{
	// private static transient Logger logger =
	org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger();
	
	private boolean redirect = true;

	@Override
	public void init(FilterConfig filterConfig) throws ServletException
	{
		// check if we are in a debug environment. If so don't do the redirect.
		if (Strings.isNotBlank(System.getenv("allow-port-80")))
			this.redirect = false;
	}

	@Override
	public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
			final FilterChain filterChain) throws IOException, ServletException
	{
		
		if (redirect && servletRequest.getScheme() =="http")
		{
			HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
			
			httpResponse.sendRedirect(new URL("https://" + servletRequest.getServerName()).toString());
		}
		else
			filterChain.doFilter(servletRequest, servletResponse);

	}

	@Override
	public void destroy()
	{
	}
}

First run wizard

Snaps don’t allow you to ask the user for input during the install (e.g. a db password) as such you will likely need your web app to implement a ‘first run wizard’ that asks the user for any critical configuration information.

Installing

Once you have built your snap you install it using the standard snap install command. Once installed you must obtain a certificate.

pre-requisites to obtaining a certificate

Before you can install a certificate your system must adhere to a no. of pre-requisites.

  1. no other app must be listening on port 80. The certificate install processes starts a temporary web server on port 80 to validate your domain. If you have Apache or nginx running on your server you must shut it down (uninstall it).

  2. you MUST have an FQDN registered for the server and a DNS A record created for the server. The certificate validation process use the FQDN to access your webserver and if it can’t connect to the temporary web server it starts it will not generate a certificate.

run getcert

Once the pre-requisites are met you are ready to obtain your certificate.

run: <yoursnapname>.getcert <email address> <fqdn>

Where: <yoursnapname> is the ‘name’ used in your snapcraft for your snap.

<email address> is the email address that Lets Encrypt should use to send notices to.

<fqdn> is the fqdn of the server you have installed your snap into.

Once the certificate is installed you should be able to access your webapp via:

https://<fqdn>

Certificate renewal

As noted above certificate renewal is automated. Current Lets Encrypt forces certificate to be renewed every 3 months. There is some talk about reducing this down to 7 days.

The certificate is renewed by an app called ‘cron’ (no not the standard cron service). This is just a little app that wakes up every fifteen minutes to see if the certificate requires renewal.

If the certificate requires renewal it will renew the certificate and then immediately restart tomcat without warning.

Logs

Tomcat logs can be found at: /var/snap/<snap name>/current/logs

You should try to be consistent and have your own app write to the same logs directory by using ${SNAP_DATA}/logs as the base for your loggers.

If you are using log4j you can use the following technique. In you a Servlet ContextListener and before you initialise Log4j:

String userCommon = System.getenv("SNAP_DATA");
System.setProperty("logPath", userCommon + "/logs");

and in your log4j2.xml

  <RollingRandomAccessFile name="RandomRollingFile" fileName="${sys:logPath}/logging.log"
                 filePattern="${sys:logPath}/%d{MM-dd-yyyy}-%i.log.gz">

Alter the version of Tomcat

The standard release comes with Tomcat 8.5, however its easy to modify your snap to use a different version of Tomcat. Minimum requirements: Tomcat 7

To modify the version, first find the Tomcat source code for the version you are interested in by viewing this page. https://github.com/apache

and entering ‘tomcat’ into the search bar.

Open the desired repository and the click the ‘clone or download’ button. Copy the URL displayed and paste into the ‘source’ line described below. Note the most most recent (non-trunk) Tag of the repo.

Here are some common versions:

https://github.com/apache/tomcat70.git
https://github.com/apache/tomcat80.git
https://github.com/apache/tomcat85.git  - we ship with this version
https://github.com/apache/tomcat.git - at the time of writing this is tomcat 9.

building from a specific tag

If you want to select a specific version of a repo (STRONGLY RECOMMENDED} then you need to also add the ‘source-tag’ attribute. We recommend that you use source-tag always.

Next add the following section under your ‘parts:’ section.

 parts:
    # build the web app
    your-webapp:
      ...
      after: [setup-repos,getcert,tomcat-with-ssl]

  tomcat-with-ssl:
    source: https://github.com/apache/tomcat.git
    source-tag: TOMCAT_9_0_5

Build your snap and you now have your preferred build of Tomcat.

1 Like

Perhaps you meant to put this in the docs category?

Hi @bsutton,

Unfortunately the snapcraft.yaml for your part contains some invalid YAML syntax which is keeping the snapcraft parts service from processing it correctly. Could you please fix it?

If you look here you’ll see two ugly red markers on lines 39 and 40, those are tab (\t) characters which YAML doesn’t like. Replace with spaces keeping the indentation and we should be good to go.

In the future I suggest using yamllint to spot this (and other!) possible YAML errors beforehand.

Thanks!

  • Daniel

done. moved to docs.

I just LOVE grammar that is based on invisible characters!!

Sorry last minute change and it was only a comment so what could that possible break :<

I’ve just ran yamllint and pushed.

Thanks.