Jetty web server customisation: common use cases
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.
Background
To provide a worked example of how to set up customJettyServers.groovy
to deal with some common embedded Jetty 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:
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:
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);
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 values for jetty.search_port
, jetty.search_port_https
and jetty.admin_port
:
jetty.search_port=
jetty.search_port_https=
jetty.admin_port=
Listed below is an example global.cfg
with notes on what to enter:
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.
Logging levels for $SEARCH_HOME/log/jetty.log*
are defined in $SEARCH_HOME/web/conf/jetty-logging.properties
.
The default logging level is set to INFO
- this can be amended to the following levels:
-
ALL
(log everything) -
SEVERE
(highest value) -
WARNING
-
INFO
-
CONFIG
-
FINE
-
FINER
-
FINEST
(lowest value) -
OFF
(log nothing)
General tips
Look for errors 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.
Custom log messages from within the customiseJettyServers.groovy
are generated using the Log.getRootLogger().LEVEL()
call. e.g.
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());