Setup an IIS reverse proxy for Atlassian Bamboo

Ever since Atlassian stopped offering Bamboo as a cloud hosted solution we have hosted our own on-premise instance. This has been working quite well, and our instance has always been accessible from the outside. Because we allow people to work from home and our other development tools are cloud hosted, this was a logical decision.

We recently migrated our SSL certificates to Let’s Encrypt. Since all our other websites are running on IIS, we used the awesome win-acme tool to handle the certificate renewal process. Just launch the app, select the IIS application for which you want to install an SSL certificate and a scheduled task will take care of the renewal process. It couldn’t be easier.

Using Let’s Encrypt on a Apache Tomcat application such as Jira or Bamboo is another story alltogether. There are some guides out there on how to achieve this on unix environments, but since we are running on Windows it’s a lot more cumbersome. So we opted to do things differently. Instead of setting everything up on the VM that runs Bamboo, we could just setup an IIS site to act as a reverse proxy, install the SSL certificate there, and configure Bamboo to run behind the reverse proxy.

This post describes the setup with respect to Bamboo, but works just as well for Jira and Confluence installations.

Setup IIS

Initial configuration

The first thing to do is to install the URL Rewrite module for IIS. Simply install the extension using the Microsoft Web Platform installer and optionally reboot the machine. Next, setup an empty IIS application and configure the correct bindings, in this example we will simply use http://bamboo.example.com for the HTTP binding and https://bamboo.example.com for the HTTPS binding.

Prepare the Rewrite module

If the rewrite module has been succesfully installed, you will find an additional configuration icon on the IIS application’s home screen.

IIS Rewrite Module

The first thing to do after opening the module is to add some server variables we will be using to handle the potential gzip compression that happens on Bamboo. In order to correctly handle this on the IIS application we will need to capture the HTTP_ACCEPT_ENCODING header and pass it along our reverse proxy rules. Open the View Server Variables screen by clicking on the menu item in the action pane on the right.

View Server Variables

Add the following two server variables:

  • HTTP_ACCEPT_ENCODING
  • HTTP_X_ORIGINAL_ACCEPT_ENCODING

Setup the rewrite rules

We could define our rules using the Rewrite module, but we need to clear the values of some variables which is not allowed from the UI. I find it easier to simply do this using notepad anyway, so create an empty web.config file in the IIS application’s folder. We will need to define a couple of rules to handle everything we want to do.

First, let’s start with the rules that handle all inbound traffic.

Since we are using Let’s Encrypt certificates and the win-acme tool, it will create a .well-known/acme-challenge folder with a random file to handle the site verification during the certificate renewal. We obviously want to exclude this from the redirect to our internal server.

<rule name="LetsEncrypt verification" stopProcessing="true">
  <match url="^acme-challenge(.*)$" ignoreCase="true" />
  <action type="None" />
</rule>

Next, we want to redirect all HTTP traffic to our HTTPS site.

<rule name="Redirect HTTP to HTTPS" enabled="true" stopProcessing="true">
  <match url=".*" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    <add input="{HTTPS}" pattern="off" />
  </conditions>
  <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" />
</rule>

Lastly, we need the rule to rewrite everything to the Bamboo URL. In our example Bamboo will be hosted at http://vm-bamboo:8085.

<rule name="Reverse Proxy Inbound Rule" stopProcessing="true">
<match url="(.*)" />
  <action type="Rewrite" url="http://vm-bamboo:8085/{R:1}" />
  <serverVariables>
    <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" />
    <set name="HTTP_ACCEPT_ENCODING" value="" />
  </serverVariables>
</rule>

First, we need to make sure the HTTP_ACCEPT_ENCODING stuff is handled correctly by our IIS application. We cleared the variable in the inbound rule, and captured the contents in the HTTP_X_ORIGINAL_ACCEPT_ENCODING variable before we forwared it to the internal server. When coming back from the internal server we want to set the HTTP_ACCEPT_ENCODING variable using the original inbound value.

<rule name="Restore Accept-Encoding" preCondition="NeedsRestoringAcceptEncoding">
  <match serverVariable="HTTP_ACCEPT_ENCODING" pattern="^(.*)" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="true" />
  <action type="Rewrite" value="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" />
</rule>

Then we can define our outbound rule for the Bamboo instance, so that all outgoing URL’s are correctly rendered using our IIS application’s binding.

<rule name="Reverse Proxy Outbound Rule" preCondition="ResponseIsHtml">
  <match filterByTags="A, Form, Img" pattern="^http(s)?://vm-bamboo:8085/(.*)" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="true" />
  <action type="Rewrite" value="http{R:1}://bamboo.example.com/{R:2}" />
</rule>

As you may have noticed, there are some preconditions defined to determine whether or not we need to restore the encoding and whether or not our outbound rule is in effect.

<preConditions>
  <preCondition name="ResponseIsHtml">
   <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
  </preCondition>
    <preCondition name="NeedsRestoringAcceptEncoding">
  <add input="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" pattern=".*" />
  </preCondition>
</preConditions>

Our final web.config file should look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <clear />
        <rule name="LetsEncrypt verification" stopProcessing="true">
          <match url="^acme-challenge(.*)$" ignoreCase="true" />
          <action type="None" />
        </rule>
        <rule name="Redirect HTTP to HTTPS" enabled="true" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
            <add input="{HTTPS}" pattern="off" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" />
        </rule>
        <rule name="Reverse Proxy Inbound Rule" stopProcessing="true">
          <match url="(.*)" />
          <action type="Rewrite" url="http://vm-bamboo:8085/{R:1}" />
          <serverVariables>
            <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" />
            <set name="HTTP_ACCEPT_ENCODING" value="" />
          </serverVariables>
        </rule>
      </rules>
      <outboundRules>
        <clear />
        <rule name="Restore Accept-Encoding" preCondition="NeedsRestoringAcceptEncoding">
          <match serverVariable="HTTP_ACCEPT_ENCODING" pattern="^(.*)" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="true" />
          <action type="Rewrite" value="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" />
        </rule>
        <rule name="Reverse Proxy Outbound Rule" preCondition="ResponseIsHtml">
          <match filterByTags="A, Form, Img" pattern="^http(s)?://vm-bamboo:8085/(.*)" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="true" />
          <action type="Rewrite" value="http{R:1}://bamboo.example.com/{R:2}" />
        </rule>
        <preConditions>
          <preCondition name="ResponseIsHtml">
            <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
          </preCondition>
          <preCondition name="NeedsRestoringAcceptEncoding">
            <add input="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" pattern=".*" />
          </preCondition>
        </preConditions>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

Setup Bamboo

Setting up Bamboo is relativly easy. Make sure to stop any running instance before making configuration changes.

In the conf folder of the Bamboo installation directory is a server.xml file we will need to change. Inside it you will find a section about a connector on which the Catalina service listens. Change it to match the following:

<Connector
  protocol="HTTP/1.1"
  port="8085"

  maxThreads="150" minSpareThreads="25"
  connectionTimeout="20000"
  disableUploadTimeout="true"
  acceptCount="100"

  enableLookups="false"
  maxHttpHeaderSize="8192"

  useBodyEncodingForURI="true"
  URIEncoding="UTF-8"

  redirectPort="8443"
  scheme="https"
  proxyName="bamboo.example.com" 
  proxyPort="443"/>

The important attributes to add to the existing connector definition are the schema, proxyName and proxyPort. In our example we configure it so that Bamboo knows we are running at https://bamboo.example.com.

The last thing to do is to configure the Base URL property of the Bamboo instance. Bamboo uses this to prefix all links it creates for emails and other outgoing messages. You can find it under the Sytem > General Configuration section in the Bamboo Administration panel. Set this to the domain name under which Bamboo is accessible: https://bamboo.example.com.

Another thing to note is that if you are running remote agents you need to make sure they are configured to use the internal server address (vm-bamboo:8085 in our example), or the new HTTPS endpoint.

comments powered by Disqus