Chapter 3. Securing the Jetty HTTP Server
Abstract
You can configure the built-in Jetty HTTP server to use SSL/TLS security by adding the relevant configuration properties to the
etc/org.ops4j.pax.web.cfg
configuration file. In particular, you can add SSL/TLS security to the Fuse Management Console in this way.
Jetty server
The AMQ container is pre-configured with a Jetty server, which acts as a general-purpose HTTP server and HTTP servlet container. Through a single HTTP port (by default,
http://Host:8181
), the Jetty container can host multiple services, for example:
- Fuse Management Console (by default,
http://Host:8181/hawtio
) - Apache CXF Web services endpoints (by default,
http://Host:8181/cxf
, if the host and port are left unspecified in the endpoint configuration) - Some Apache Camel endpoints
If you use the default Jetty server for all of your HTTP endpoints, you can conveniently add SSL/TLS security to these HTTP endpoints by following the steps described here.
Create X.509 certificate and private key
Before you can enable SSL, you must create an X.509 certificate and private key for the Web console. The certificate and private key must be in Java keystore format. For details of how to create a signed certificate and private key, see Appendix A, Managing Certificates.
Enabling SSL/TLS for Jetty in a standalone container
To enable SSL/TLS for Jetty in a standalone (non-Fabric) Karaf container:
- Open
etc/org.ops4j.pax.web.cfg
in a text editor. - Replace the original content of the
etc/org.ops4j.pax.web.cfg
file with the following settings:# Configures the SMX Web Console to use SSL org.ops4j.pax.web.config.file=etc/jetty.xml org.osgi.service.http.enabled=false org.osgi.service.http.port=8181 org.ops4j.pax.web.session.cookie.httpOnly=true org.osgi.service.http.secure.enabled=true org.osgi.service.http.port.secure=8443 org.ops4j.pax.web.ssl.keystore=etc/alice.ks org.ops4j.pax.web.ssl.password=alicepass org.ops4j.pax.web.ssl.keypassword=alicepass
Where the new settings disable the existing insecure HTTP port (on 8181) and enable a new secure HTTPS port (on 8443). - Customize the SSL/TLS settings in
etc/org.ops4j.pax.web.cfg
as follows:org.osgi.service.http.port.secure
- Specifies the TCP port number of the secure HTTPS port.
org.ops4j.pax.web.ssl.keystore
- The location of the Java keystore file on the file system. Relative paths are resolved relative to the
KARAF_HOME
environment variable (by default, the install directory). org.ops4j.pax.web.ssl.password
- The store password that unlocks the Java keystore file.
org.ops4j.pax.web.ssl.keypassword
- The key password that decrypts the private key stored in the keystore (usually the same as the store password).
- Restart the AMQ container, in order for the configuration changes to take effect.
Customizing allowed TLS protocols and cipher suites
You can customize the allowed TLS protocols and cipher suites by setting the following properties in the
etc/org.ops4j.pax.web.cfg
file:
org.ops4j.pax.web.ssl.protocols.included
- Specifies a list of allowed TLS/SSL protocols.
org.ops4j.pax.web.ssl.protocols.excluded
- Specifies a list of disallowed TLS/SSL protocols.
org.ops4j.pax.web.ssl.ciphersuites.included
- Specifies a list of allowed TLS/SSL cipher suites.
org.ops4j.pax.web.ssl.ciphersuites.excluded
- Specifies a list of disallowed TLS/SSL cipher suites.
For full details of the available protocols and cipher suites, consult the appropriate JVM documentation and security provider documentation. For example, for Java 7, see Java Cryptography Architecture Oracle Providers Documentation for Java Platform Standard Edition 7.
Connect to the secure console
After configuring SSL security for the Jetty server in the Pax Web configuration file, you should be able to open the Fuse Management Console by browsing to the following URL:
https://Host:8443/hawtio
Note
Remember to type the
https:
scheme, instead of http:
, in this URL.
Initially, the browser will warn you that you are using an untrusted certificate. Skip this warning and you will be presented with the login screen for the Fuse Management Console.
Advanced Jetty security configuration
In order to have more control over the Jetty security settings, you can enable Jetty security by modifying the configuration settings in the
etc/jetty.xml
file. This approach gives you access to the full Jetty security API:
- Open
etc/org.ops4j.pax.web.cfg
in a text editor. - Disable the insecure HTTP port by adding the org.osgi.service.http.enabled and setting it to
false
; and enable the secure HTTPS port by adding the org.osgi.service.http.secure.enabled and setting it totrue
. Change the value oforg.ops4j.pax.web.config.file
to reference the file,etc/jetty-ssl.xml
(which you will create in the next step).Theetc/org.ops4j.pax.web.cfg
file should now have the following contents:# Configures the SMX Web Console to use SSL org.ops4j.pax.web.config.file=etc/jetty-ssl.xml org.osgi.service.http.enabled=false org.osgi.service.http.port=8181 org.ops4j.pax.web.session.cookie.httpOnly=true org.osgi.service.http.secure.enabled=true
- Create a new file,
etc/jetty-ssl.xml
, with the following contents:<?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd"> <Configure id="Server" class="org.eclipse.jetty.server.Server"> <!-- ========================================================== --> <!-- Set connectors --> <!-- ========================================================== --> <!-- One of each type! --> <!-- ========================================================== --> <!-- Use this connector for many frequently idle connections and for threadless continuations. --> <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration"> <Set name="secureScheme">https</Set> <Set name="securePort"> <Property name="jetty.secure.port" default="8443" /> </Set> <Set name="outputBufferSize">32768</Set> <Set name="requestHeaderSize">8192</Set> <Set name="responseHeaderSize">8192</Set> <Set name="sendServerVersion">true</Set> <Set name="sendDateHeader">false</Set> <Set name="headerCacheSize">512</Set> </New> <!-- ========================================================== --> <!-- Configure Authentication Realms --> <!-- Realms may be configured for the entire server here, or --> <!-- they can be configured for a specific web app in a context --> <!-- configuration (see $(jetty.home)/contexts/test.xml for an --> <!-- example). --> <!-- ========================================================== --> <Call name="addBean"> <Arg> <New class="org.eclipse.jetty.jaas.JAASLoginService"> <Set name="name">karaf</Set> <Set name="loginModuleName">karaf</Set> <Set name="roleClassNames"> <Array type="java.lang.String"> <Item> org.apache.karaf.jaas.boot.principal.RolePrincipal </Item> </Array> </Set> </New> </Arg> </Call> <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration"> <Arg><Ref refid="httpConfig"/></Arg> <Call name="addCustomizer"> <Arg> <New class="org.eclipse.jetty.server.SecureRequestCustomizer"/> </Arg> </Call> </New> <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory"> <Set name="KeyStorePath"> /home/jdoe/Programs/JBossFuse/jboss-fuse-6.3.0.redhat-187/etc/alice.ks </Set> <Set name="KeyStorePassword">alicepass</Set> <Set name="KeyManagerPassword">alicepass</Set> <!--Set name="TrustStorePath"> <Property name="jetty.base" default="." /> <Property name="jetty.truststore" default="quickstarts/switchyard/demos/policy-security-basic/connector.jks"/> </Set> <Set name="TrustStorePassword"> <Property name="jetty.truststore.password" default="changeit"/> </Set--> <Set name="EndpointIdentificationAlgorithm"></Set> <Set name="NeedClientAuth"> <Property name="jetty.ssl.needClientAuth" default="false"/> </Set> <Set name="WantClientAuth"> <Property name="jetty.ssl.wantClientAuth" default="false"/> </Set> <!-- Disable SSLv3 to protect against POODLE bug --> <Set name="ExcludeProtocols"> <Array type="java.lang.String"> <Item>SSLv3</Item> </Array> </Set> <Set name="ExcludeCipherSuites"> <Array type="String"> <Item>SSL_RSA_WITH_DES_CBC_SHA</Item> <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item> <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item> <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item> <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item> <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item> <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item> </Array> </Set> </New> <Call id="httpsConnector" name="addConnector"> <Arg> <New class="org.eclipse.jetty.server.ServerConnector"> <Arg name="server"><Ref refid="Server" /></Arg> <Arg name="acceptors" type="int"> <Property name="ssl.acceptors" default="-1"/> </Arg> <Arg name="selectors" type="int"> <Property name="ssl.selectors" default="-1"/> </Arg> <Arg name="factories"> <Array type="org.eclipse.jetty.server.ConnectionFactory"> <Item> <New class="org.eclipse.jetty.server.SslConnectionFactory"> <Arg name="next">http/1.1</Arg> <Arg name="sslContextFactory"> <Ref refid="sslContextFactory"/> </Arg> </New> </Item> <Item> <New class="org.eclipse.jetty.server.HttpConnectionFactory"> <Arg name="config"><Ref refid="sslHttpConfig"/></Arg> </New> </Item> </Array> </Arg> <Set name="name">0.0.0.0:8443</Set> <Set name="host"><Property name="jetty.host" /></Set> <Set name="port"> <Property name="https.port" default="8443" /> </Set> <Set name="idleTimeout"> <Property name="https.timeout" default="30000"/> </Set> <Set name="soLingerTime"> <Property name="https.soLingerTime" default="-1"/> </Set> <Set name="acceptorPriorityDelta"> <Property name="ssl.acceptorPriorityDelta" default="0"/> </Set> <Set name="selectorPriorityDelta"> <Property name="ssl.selectorPriorityDelta" default="0"/> </Set> <Set name="acceptQueueSize"> <Property name="https.acceptQueueSize" default="0"/> </Set> </New> </Arg> </Call> </Configure>
ImportantThe preceding configuration explicitly disables the SSLv3 protocol, in order to safeguard against the Poodle vulnerability (CVE-2014-3566). For more details, see Disabling SSLv3 in JBoss Fuse 6.x and JBoss A-MQ 6.x. - (Optional) If you prefer, you can use a system property to help you specify the location of the Java keystore file. For example, instead of setting the
KeyStorePath
property explicitly (in the precedingetc/jetty-ssl.xml
configuration):<Set name="KeyStorePath">/home/jdoe/Documents/jetty.ks</Set>
You could use thekaraf.home
system property to specify the location of the keystore file relative to the AMQ install directory:<Set name="KeyStorePath"> <SystemProperty name="karaf.home"/>/etc/jetty.ks </Set>
- Customize the properties of the
SslContextFactory
instance defined in theetc/jetty-ssl.xml
file, as follows:KeyStorePath
- The location of the Java keystore file on the file system. Relative paths are resolved relative to the
KARAF_HOME
environment variable (by default, the install directory). KeyStorePassword
- The store password that unlocks the Java keystore file.
KeyManagerPassword
- The key password that decrypts the private key stored in the keystore (usually the same as the store password).
- Restart the AMQ container, in order for the configuration changes to take effect.NoteThe Apache Karaf container does not automatically detect changes in the
etc/jetty-ssl.xml
file. Hence, if you make subsequent edits to theetc/jetty-ssl.xml
file, you must also update theetc/org.ops4j.pax.web.cfg
file (by making a trivial edit or using the UNIXtouch
command), in order to force Apache Karaf to reload theetc/jetty-ssl.xml
file.
Enabling SSL/TLS for Jetty in a Fabric
Securing Jetty in a Fabric is slightly more complicated than securing Jetty in a standalone Karaf container, because each container must also be configured as a secure client of the Jetty HTTP server. For example, whenever a new container is provisioned in a Fabric, it downloads artifacts by connecting to the Maven proxy through the Jetty HTTPS port on the root container. Hence, each container in the Fabric must be configured to trust the HTTPS connection to the root container (by configuring a trust store).
Note
The procedure described here assumes that you are about to create a Fabric from scratch. It is generally not feasible to add SSL/TLS security to a pre-existing Fabric, because this puts you in a Catch-22 situation with respect to provisioning the containers.
To enable SSL/TLS for Jetty in a Fabric:
- Under the root container's installation directory, create the new directory,
etc/certs
. - In the
etc/certs
directory, create a new self-signed certificate and private key using the Javakeytool
utility, as follows:keytool -genkeypair -keyalg RSA -dname "CN=Hostname" -ext SubjectAlternativeName=ip:PUBLIC_IP -validity 365 -keystore alice.ks -alias alice -keypass KeyPass -storepass StorePass
After executing this command, the key pair is stored in thealice.ks
keystore file under the alias,alice
. Pay particular attention to theHostname
value and thePUBLIC_IP
value: the specifiedHostname
must be the name of the host where the root container is deployed andPUBLIC_IP
is the public IP address. The other Fabric containers will check that the certificate's Common Name (CN) matches the root container's host name during the SSL/TLS handshake.For a more detailed explanation of key pairs and instructions for (optionally) signing the resulting certificate with a Certificate Authority (CA), see Appendix A, Managing Certificates.NoteIf there are multiple containers (Fabric servers) in the Fabric ensemble, you must create and deploy a separate key pair for each container in the ensemble, where the specifiedHostname
matches the respective container host. The other containers in the Fabric must then be configured to trust all of the ensemble certificates (which you could do, for example, by adding all of the ensemble certificates to a trust store file accessible to the other containers). - Start up the root container:
./bin/fuse
- Create a new fabric, by entering a console command like the following:
JBossFuse:karaf@root> fabric:create --new-user AdminUser --new-user-password AdminPass --new-user-role Administrator --global-resolver manualip --resolver manualip --manual-ip Hostname --zookeeper-password ZooPass --wait-for-provisioning
ImportantTheHostname
value specifed infabric:create
must be exactly the sameHostname
value that was assigned to the CN field of the certificate in step 2. Otherwise, when you create a new child container, the hostname check will fail during the SSL/TLS handshake and the child container will fail to provision.NoteIn a production system (and for any long-running demonstration system), the Fabric server must be deployed on a host that has a static IP address. - Edit the Jetty Web server properties for the
org.ops4j.pax.web
persistent ID in thedefault
profile. You can edit these properties either from the Fuse Management Console (by navigating tohttp://localhost:8181/hawtio
in your browser) or using the built-in editor at the console:JBossFuse:karaf@root> profile-edit --resource org.ops4j.pax.web.properties default
Add the following settings to the existing content of theorg.ops4j.pax.web.properties
resource:... org.osgi.service.http.enabled=false org.osgi.service.http.secure.enabled=true org.osgi.service.http.port.secure=${port:8443,8543} org.ops4j.pax.web.ssl.keystore=AbsolutePathToKeystoreFile org.ops4j.pax.web.ssl.password=StorePass org.ops4j.pax.web.ssl.keypassword=KeyPass
Customize theorg.ops4j.pax.web
settings as follows:org.osgi.service.http.enabled
- Set to
false
, to disable the insecure Jetty HTTP port. org.osgi.service.http.secure.enabled
- Set to
true
, to enable the secure Jetty HTTPS port. org.osgi.service.http.port.secure
- Specifies the TCP port number of the secure HTTPS port. You should use the Fabric port service (see section "The Port Service" in "Fabric Guide"), which enables you to specify a range of ports for this setting,
${port:8443,8543}
. This ensure that any child containers are automatically allocated unique port numbers. org.ops4j.pax.web.ssl.keystore
- The location of the Java keystore file on the file system. This should be specified as an absolute pathname, to ensure that both the root container and child containers can locate the keystore file (child containers evaluate relatives paths differently from the root container). For example, a typical setting might look like this:
org.ops4j.pax.web.ssl.keystore=/opt/servers/jboss-fuse-6.3.0.redhat-187/etc/certs/alice.ks
org.ops4j.pax.web.ssl.password
- The store password that unlocks the Java keystore file.
org.ops4j.pax.web.ssl.keypassword
- The key password that decrypts the private key stored in the keystore (usually the same as the store password).
- Create a truststore file for the child containers. There are a few different approaches you can take when creating the truststore:
- The simplest option is to use the keystore file—for example,
etc/certs/alice.ks
—directly as the truststore. - If you need to trust multiple certificates, extract the
alice
certificate from thealice.ks
truststore and add it to an existing truststore file which contains all of the other certificates you want to trust. - If you signed the
alice
certificate with a CA, you can add the CA certificate to the truststore file.
- The current instructions apply to a fabric that has only one container in its ensemble (the root container). If you set up a fabric with three ensemble servers, however, you would need to make sure that you configure the truststores so that each ensemble server trusts the other two. For example, with three ensemble servers:
- Add public keys from servers 1 and 2 to truststore for server 3.
- Add public keys from servers 2 and 3 to truststore for server 1.
- Add public keys from servers 3 and 1 to truststore for server 2.
Alternatively, if you have set up a certificate authority (CA), a more practical approach would be to sign all of the certificates with the same CA certificate and then put the CA certificate into the truststore (that is, in this case only the CA certificate needs to be in the truststore and the same truststore can be used on all of the hosts). - Shut down the root container (for example, by entering
shutdown -f
at the console) and specify the truststore and truststore password on the root container. To specify the truststore as a JVM argument, edit the root container'setc/setenv
file and add the following line:EXTRA_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/servers/jboss-fuse-6.3.0.redhat-187/etc/certs/alice.ks -Djavax.net.ssl.trustStorePassword=StorePass"
Where this example assumes you are using thealice.ks
file directly as the truststore. - Restart the root container. Search the log (for example, by entering the
log:display
console command) and look for a line like the following:17:37:35,576 | INFO | pool-3-thread-1 | JettyServerImpl | 117 - org.ops4j.pax.web.pax-web-jetty - 4.2.6 | Pax Web available at [0.0.0.0]:[8453]
This gives you the port number of the secure Jetty Web server. You can login to the Fuse Management Console using this port—for example, using a URL like the following (not forgetting to specify the scheme ashttps
):https://Host:8543
- You can now create a new child container with Jetty security enabled, by specifying the truststore and truststore password as JVM arguments when you create the child container. For example, assuming that you are using the
alice.ks
file directly as a truststore, you can create a secure child container with a command like the following:JBossFuse:karaf@root> container-create-child --jvm-opts='-Djavax.net.ssl.trustStore=/opt/servers/jboss-fuse-6.3.0.redhat-187/etc/certs/alice.ks -Djavax.net.ssl.trustStorePassword=StorePass' --profile fabric root child
- Check the provision status of the new child using the
fabric:container-list
console command (or by monitoring the Container tab of the Fuse Management Console). If the child fails to provision, check the logs of both the root container and the child container for errors.
References
The Jetty server provides flexible and sophisticated options for configuring security. You can exploit these advanced options by editing the
etc/jetty-ssl.xml
file and configuring it as described in the Jetty security documentation: