Home > Software engineering >  Redirect HTTP to HTTPS for Jenkins hosted on Tomcat server?
Redirect HTTP to HTTPS for Jenkins hosted on Tomcat server?

Time:08-22

I have jenkins.war deployed on tomcat 9(on Linux) and configured it for http and https.

Configuration on server.xml

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
           redirectPort="8443" />
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true">
    <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/ssl/test.keystore"
                     type="RSA" certificateKeystorePassword="changeit"/>
    </SSLHostConfig>
</Connector>

Configuration on web.xml

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

I'm able to redirect http to https with above configuration for tomcat when jenkins wasn't hosted. But after deploying jenkins.war redirecting http to https is not working.

Are there any other configuration changes required for redirecting http to https for jenkins?

CodePudding user response:

If you add a security constraint to `$CATALINA_BASE/conf/web.xml' that that constraint will combine with any constraints defined in the web application using the rules for combining constraints defined in the Servlet specification.

Jenkins defines the follow security constraint by default:

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>other</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <!-- no security constraint --> 
  </security-constraint>

The relevant rule from the Servlet specification is:

The combination of user-data-constraints that apply to a common url-
pattern and http-method shall yield the union of connection types accepted by
the individual constraints as acceptable connection types. A security constraint that
does not contain a user-data-constraint shall combine with other user-data-
constraint to cause the unprotected connection type to be an accepted connection
type.

Hence The Jenkins rule combines with your rule and the result is that unprotected (http) connections are allowed.

Possible solutions include:

  • Remove the security constraint from the web.xml file packaged in jenkins.war
  • Edit the security constraint from the web.xml file packaged in jenkins.war
  • Deploy a custom Valve or Filter to enforce the redirection

CodePudding user response:

Based on the notes in the answer from Mark Thomas, I tried two different approaches, both of which worked for me.

Both approaches also require the changes you already described in your question.


Approach One

Editing the security constraint in the Jenkins WAR.

For this I let Tomcat explode the WAR in the webapps directory, and then edited the file at webapps/jenkins/WEB-INF/web.xml. I commented out the following section:

  <!-- commented out this part...
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>other</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
  -->   
    <!-- no security constraint --> 
  <!-- ...and this part </security-constraint> -->

Approach Two

I created a Tomcat valve, which I created as a JAR file, and then placed in Tomcat's main lib directory.

I added a valve element to Tomcat, to activate the valve.

The specific steps:

  1. I used the most recent version of Jenkins LTS (long term support) - version 2.346.3. Because this version of Jenkins does not support jakarta packages, I installed it on Tomcat 9 (for javax packages).

  2. I created a standard Java library project, one which is packaged into a JAR (not a WAR). The Maven POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.ajames.tomcathttpsvalve</groupId>
    <artifactId>TomcatHttpsValve</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <exec.mainClass>org.ajames.tomcathttpsvalve.TomcatHttpsValve</exec.mainClass>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>9.0.65</version>
        </dependency>
    </dependencies>
</project>

You may need to adjust the compiler settings for your version of Java.

  1. I created the following valve class:
package org.ajames.tomcathttpsvalve;

import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;

// In server.xml:
// <Valve className="org.ajames.tomcathttpsvalve.TomcatHttpsValve"
//        fromPort="8080" toPort="8443" />
//
// This class is packaged as a JAR & placed in Tomcat's /lib
public class TomcatHttpsValve extends ValveBase {

    private final String fromScheme = "http";
    private final String toScheme = "https";
    private final int httpsDefaultPort = 443;
    private int fromPort; // see valve config for value
    private int toPort; // see valve config for value

    public TomcatHttpsValve() {
        super(true); // async supported
    }

    @Override
    public void invoke(Request req, Response resp) throws IOException, ServletException {
        if (req.getScheme().toLowerCase().equals(toScheme)) {
            // already using https:
            getNext().invoke(req, resp);
        } else {
            // need to redirect to https:
            resp.sendRedirect(buildNewReqUrl(req));
        }
    }

    private String buildNewReqUrl(Request req) {
        String scheme = req.getScheme();
        int port = req.getServerPort();

        if (scheme.toLowerCase().equals(fromScheme)) {
            scheme = toScheme;
        }

        if (port == fromPort) {
            port = toPort;
        }

        // build the new URL
        // assumes no userinfo (...//john.doe@...)
        StringBuilder sb = new StringBuilder();
        sb.append(scheme).append("://").append(req.getServerName());
        if (port != httpsDefaultPort) { // 443 is implicit with https
            sb.append(":").append(port);
        }
        sb.append(req.getRequestURI()); // (e.g. /foo/bar)
        if (req.getQueryString() != null) {
            sb.append("?").append(req.getQueryString());
        }

        return sb.toString();
    }

    public int getFromPort() {
        return fromPort;
    }

    public void setFromPort(String fromPort) {
        this.fromPort = Integer.parseInt(fromPort);
    }

    public int getToPort() {
        return toPort;
    }

    public void setToPort(String toPort) {
        this.toPort = Integer.parseInt(toPort);
    }
}

This intercepts the request URL and (if needed) redirects to a modified URL.

I placed the JAR file containing this class in the main Tomcat lib directory.

  1. I created a new valve element:
<Valve className="org.ajames.tomcathttpsvalve.TomcatHttpsValve"
       fromPort="8080" toPort="8443" />

In my case, I chose to add this to the <host> section of my Tomcat's server.xml file. This means that the valve applies to all requests coming in to that host (in my case, localhost).

You can also place this inside the <engine> section if you want to affect all traffic.

(You could also place this inside a specific <context> section, if you want more granular control - for a specific web application. But this would require you to customize the Jenkins installation, similar to Approach One.)


Using a Tomcat valve ties you to using Tomcat as the container for your Jenkins application. But you do not need to make any changes to Jenkins.

You do, of course, need to remember to re-apply these customizations when upgrading Tomcat. And if upgrading to Tomcat 10 , you would also need to re-build the valve using jakarta packages instead of javax packages (with the relevant version of the tomcat-catalina dependency).

  • Related