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 JBoss Fuse 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:
  1. Open etc/org.ops4j.pax.web.cfg in a text editor.
  2. 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).
  3. 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).
  4. Restart the JBoss Fuse 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:
  1. Open etc/org.ops4j.pax.web.cfg in a text editor.
  2. 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 to true. Change the value of org.ops4j.pax.web.config.file to reference the file, etc/jetty-ssl.xml (which you will create in the next step).
    The etc/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
  3. 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>
    Important
    The 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.
  4. (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 preceding etc/jetty-ssl.xml configuration):
    <Set name="KeyStorePath">/home/jdoe/Documents/jetty.ks</Set>
    You could use the karaf.home system property to specify the location of the keystore file relative to the JBoss Fuse install directory:
    <Set name="KeyStorePath">
        <SystemProperty name="karaf.home"/>/etc/jetty.ks
    </Set>
  5. Customize the properties of the SslContextFactory instance defined in the etc/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).
  6. Restart the JBoss Fuse container, in order for the configuration changes to take effect.
    Note
    The Apache Karaf container does not automatically detect changes in the etc/jetty-ssl.xml file. Hence, if you make subsequent edits to the etc/jetty-ssl.xml file, you must also update the etc/org.ops4j.pax.web.cfg file (by making a trivial edit or using the UNIX touch command), in order to force Apache Karaf to reload the etc/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:
  1. Under the root container's installation directory, create the new directory, etc/certs.
  2. In the etc/certs directory, create a new self-signed certificate and private key using the Java keytool 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 the alice.ks keystore file under the alias, alice. Pay particular attention to the Hostname value and the PUBLIC_IP value: the specified Hostname must be the name of the host where the root container is deployed and PUBLIC_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.
    Note
    If 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 specified Hostname 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).
  3. Start up the root container:
    ./bin/fuse
  4. 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
    Important
    The Hostname value specifed in fabric:create must be exactly the same Hostname 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.
    Note
    In 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.
  5. Edit the Jetty Web server properties for the org.ops4j.pax.web persistent ID in the default profile. You can edit these properties either from the Fuse Management Console (by navigating to http://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 the org.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 the org.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).
  6. 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 the alice.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.
  7. 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).
  8. 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's etc/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 the alice.ks file directly as the truststore.
  9. 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 as https):
    https://Host:8543
  10. 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
  11. 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: