Some Background

Jenkins is a platform for build automation, and as such allows you to store the results of the build (the binaries, commonly known as "artifacts") for later.

I saw other people on the internet manually downloading their results via the web UI:

However, I wanted to pull the artifacts from my automation platform (currently Chef), and ran into an authentication predicament.

The Problem

Normally, when using HTTP basic authentication, there's a "challenge-response" mechanism, looking something like this:

However, Jenkins doesn't challenge clients for credentials (response 401), and instead fails immediatly (response 403).
This is called "Preemptive authentication", and is considered a bad habit because the client hands out credentials when it's not definitely required. Therefore, most clients require special configuration to handle this.
For instance, when using wget, one can use:

wget --auth-no-challenge --http-user=USER --http-password=BESTPASS http://server/jenkins

However, when using any tool where the URL is implicit, such as PIP or Chef's remote_file, I can only provide credentials by specifying them in the URL (e.g. http://back:slasher@private.com/repo), so I don't have any way of modifying the authentication method.

The result - I can't download artifacts directly from Jenkins, messing up my deployment cookbooks.
I thought about storing the results in a secondary server to act as a repo, but I really liked having Jenkins automatic maintenance (only keeping the last successful build's artifact) and the simplicity of downloading from Jenkins directly, because less steps in building-downloading-installing means less places to fail.

The Solution

I ended up using Apache on the Jenkins server as a credentials-requiring-proxy, meaning that:

  1. Apache will require credentials using the good-old "challenge-response" method
  2. Apache will forward the request, including the now-provided crednetials, to Jenkins
  3. Jenkins will do it's thing, providing the latest build's artifacts

The setup was as pretty standard reverse proxy, except for the authentication part - I needed Apache to require credetials, but accept any non-empty set.
I used mod_authn_anon to require authentication with * as the value, causing it to accept any user/password provided.
The result looks like this:

<VirtualHost *:1234>
<Location />
    # Allow any user, but require one
    Anonymous *
    AuthType basic
    AuthName 'Jenkins Proxy'
    AuthBasicProvider anon
    Require valid-user
</Location>
ProxyPass / http://localhost:1111/
</VirtualHost>

Where http://localhost:1111/ is Jenkins' normal web UI.
Since I only use "end" URLs (as in not following redirections from the server), I didn't need to add a ProxyPassReverse directive like ProxyPassReverse / http://localhost:1111/, which causes Apache to rewrite HTTP headers to match the proxy rather than the original server.

I won't post the entire apache configuration, because it's pretty trivial. However, the modules I needed are:

  • mod_proxy
  • mod_proxy_http
  • mod_auth_basic
  • mod_authn_anon

Now everything works and I'm happy.

Attribution

  • HTTP Authentication diagram from Oracle
  • Wget configuration for Jenkins - Ed Bragg