Jetty web server customisation: common use cases

Managed by | Updated .

Warning
Care should be taken when applying customisation to the Jetty web server. A malformed script may prevent the Jetty web server from starting which will cause Funnelback's administration and search interfaces to stop functioning.

Prerequisites 

The following article assumes that a working Funnelback 15 server is available. The term $SEARCH_HOME is used throughout the document and this usually refers to either /opt/funnelback (Linux) or <drive>:\funnelback (Windows).

Before starting check your Funnelback version and the corresponding documentation to ensure that the CustomiseJettyServers.groovy script is available.

Objective

To provide a worked example of how to set up customJettyServers.groovy to deal with some common embeddedJetty customisations, i.e. host custom keystore/truststore, excluding protocols and including Cipher Suites.

Steps

  • Login into the server that Funnelback is installed.
  • Create $SEARCH_HOME/web/conf/customiseJettyServers.groovy in a text editor.
  • Enter the following into the file:
$SEARCH_HOME/web/conf/customiseJettyServers.groovy
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.log.Log;

def removeConnectors(Map<String, Server> servers, String serverName) {

    //Finding curently set connectors for the  server context
    Connector[] publicConnectors = servers.get(serverName).getConnectors();


    //Removing ALL set connectors - to be replaced by the ones we create later!
    publicConnectors.each {
        servers.get("public").removeConnector(it);
    }
}

def HttpConnectionFactory createHttpConnectionFactory() {
    HttpConfiguration httpConfig = new HttpConfiguration();
    httpConfig.setOutputBufferSize(32768);
    httpConfig.setRequestHeaderSize(8192);
    httpConfig.setResponseHeaderSize(8192);
    HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig);
    return httpConnectionFactory;
}


def SslContextFactory createSslContextFactory(String keyStorePath, String keyStorePassword, String trustStorePath,String trustStorePassword,String[] excludeProtocols,String[] includeCipherSuites) {

    SslContextFactory sslContextFactory = new SslContextFactory();
    sslContextFactory.setKeyStorePath(keyStorePath);
    sslContextFactory.setKeyStorePassword(keyStorePassword);
    sslContextFactory.setTrustStorePath(trustStorePath);
    sslContextFactory.setTrustStorePassword(trustStorePassword);

    sslContextFactory.addExcludeProtocols(excludeProtocols);
    sslContextFactory.setIncludeCipherSuites(includeCipherSuites);

    return sslContextFactory;
} 


def Map<String, Server> customise(Map<String, Server> servers) {
    //Removing existing connectors for the public server context
    removeConnectors(servers, "public");

    // Add a HTTP connector to the public server
    ServerConnector httpConnector = new ServerConnector(servers.get("public"), createHttpConnectionFactory());
    httpConnector.setPort(9080);

    String[] excludeProtocols = ["SSL","SSLv2","SSLv2Hello","SSLv3"];
    String[] includeCipherSuites = ["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA"];

    //SslContextFactory contstruction - to be shared by both admin and public; create a separate one if the properties used are different
    SslContextFactory sslContextFactory = createSslContextFactory(
              <$SEARCH_HOME>/web/conf/keystore","OBF:<keystore password>",
              <$SEARCH_HOME>/web/conf/keystore (** truststore **)","OBF:<trustore password>",
              excludeProtocols,includeCipherSuites);

    //An an SSL Connector to the public server context
    ServerConnector publicSslConnector = new ServerConnector(servers.get("public"), sslContextFactory);
    publicSslConnector.setPort(9843);

    //Adding http and https connectors to the public context
    servers.get("public").addConnector(httpConnector);
    servers.get("public").addConnector(publicSslConnector);

    Log.getRootLogger().info("CustomiseJettyServers - public server info:");
    Log.getRootLogger().info("Public SSL Exclude Cipher Suites:"+sslContextFactory.getExcludeCipherSuites());
    Log.getRootLogger().info("Public SSL Include Cipher Suites:"+sslContextFactory.getIncludeCipherSuites());
    Log.getRootLogger().info("Public Port:"+publicSslConnector.getPort());

    //Finding currently set connectors for the admin server context
    removeConnectors(servers,"admin");  

    //Add an SSL Connector to the admin server context
    ServerConnector adminSslConnector = new ServerConnector(servers.get("admin"), sslContextFactory);
    adminSslConnector.setPort(8443);
    servers.get("admin").addConnector(adminSslConnector);

    Log.getRootLogger().info("CustomiseJettyServers - admin server info:");
    Log.getRootLogger().info("Admin SSL Exclude Cipher Suites:"+sslContextFactory.getExcludeCipherSuites());
    Log.getRootLogger().info("Admin SSL Include Cipher Suites:"+sslContextFactory.getIncludeCipherSuites());
    Log.getRootLogger().info("Admin Port:"+adminSslConnector.getPort());
  
    return servers;
}

The code above will allow you to set the following up:

  • Custom keystore and truststore:
Custom Keystore and Truststore
def SslContextFactory createSslContextFactory(String keyStorePath, String keyStorePassword, String trustStorePath,String trustStorePassword,String[] excludeProtocols,String[] includeCipherSuites) {

    SslContextFactory sslContextFactory = new SslContextFactory();
    sslContextFactory.setKeyStorePath(keyStorePath);
    sslContextFactory.setKeyStorePassword(keyStorePassword);
    sslContextFactory.setTrustStorePath(trustStorePath);
    sslContextFactory.setTrustStorePassword(trustStorePassword);

    sslContextFactory.addExcludeProtocols(excludeProtocols);
    sslContextFactory.setIncludeCipherSuites(includeCipherSuites);

    return sslContextFactory;
} 
:
:
:
SslContextFactory sslContextFactory = createSslContextFactory(
              <$SEARCH_HOME>/web/conf/keystore","OBF:<keystore password>",
              <$SEARCH_HOME>/web/conf/keystore (** truststore **)","OBF:<trustore password>",
              excludeProtocols,includeCipherSuites);
  • Custom cipher suites and protocols:
Cipher Suite Inclusion and Protocol Exclusion
:
:
    String[] excludeProtocols = ["SSL","SSLv2","SSLv2Hello","SSLv3"];
    String[] includeCipherSuites = ["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA"];
:
:

To ensure that ports don't conflict on Jetty startup, edit $SEARCH_HOME/conf/global.cfg and remove off values for jetty.search_port, jetty.search_port_https and  jetty.admin_port so that the look like:

jetty.search_port=
jetty.search_port_https=
jetty.admin_port=

Listed below is an example global.cfg with notes on what to enter:

$SEARCH_HOME/conf/global.cfg
mail.smtp.host=localhost
mail.smtp.port=25
mail.smtp.auth=false
platform.bitness=64bit
mail.smtp.user=
mail.smtp.password=
jetty.search_port=
jetty.search_port_https= 
jetty.admin_port=
:
:
urls.search_port=<set to the same port as jetty.search_port >
urls.admin_port=<set to the CUSTOM admin port number>
:
:

Once $SEARCH_HOME/web/conf/customiseJettyServers.groovy and global.cfg have been edited, restart the jetty service.

Check the $SEARCH_HOME/log/jetty.log.* files for progress and for any errors.

Troubleshooting

jetty-logging.properties

Sometimes more detail can be gleaned by increasing the logging level found in Jetty.  

  • Set logging levels for $SEARCH_HOME/log/jetty.log*  from  $SEARCH_HOME/web/conf/jetty-logging.properties
    • By default the logging level is set to INFO, but as its using java.util.logging.FileHandler as the base logger, use the following level settings (Source: https://docs.oracle.com/javase/8/docs/api/java/util/logging/Level.html)
      • SEVERE (highest value)
      • WARNING
      • INFO
      • CONFIG
      • FINE 
      • FINER
      • FINEST (lowest value)
    • In addition there is a level OFF that can be used to turn off logging, and a level ALL that can be used to enable logging of all messages.
  • Listed below is an example of jetty-logging.properties set to FINE level.
$SEARCH_HOME/web/conf/jetty-logging.properties
handlers=java.util.logging.FileHandler

java.util.logging.FileHandler.level=FINE
# Working dir is tools/jetty/ (overwritten by the launcher anyway)
java.util.logging.FileHandler.pattern=../../log/jetty.log

# Write 10MB before rotating this file
java.util.logging.FileHandler.limit=10000000

# Append when file already exists
java.util.logging.FileHandler.append=true

# Number of rotating files to be used
java.util.logging.FileHandler.count=10
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter

.level=FINE

General Tips

  • Look for tell-tale signs of syntax errors emanating from customiseJettyServers.groovy in the jetty logs.  If a syntax error is detected in the customiseJettyServers.groovy script, it WILL NOT EXECUTE.
  • If customiseJettyServers.groovy contains code, the following will be seen in the $SEARCH_HOME/log/jetty.log*:
    • SEVERE: *** Server config has been altered with /opt/funnelback/web/conf/customiseJettyServers.groovy ***
    • Syntax errors in customiseJettyServers.groovy (if they exist) will appear below the above message in quite close proximity.
  • Set logging messages from within the customiseJettyServers.groovy script.  Examples can be seen in the sample script above e.g.
Sample Logging
    Log.getRootLogger().info("CustomiseJettyServers - public server info:");
    Log.getRootLogger().info("Public SSL Exclude Cipher Suites:"+sslContextFactory.getExcludeCipherSuites());
    Log.getRootLogger().info("Public SSL Include Cipher Suites:"+sslContextFactory.getIncludeCipherSuites());
    Log.getRootLogger().info("Public Port:"+publicSslConnector.getPort());
Was this artcle helpful?