Abstract
Concourse is a continuous integration (CI) server. It can be deployed manually or via BOSH.
In this blog post, we describe the BOSH deployment of a Concourse CI server to natively accept Secure Sockets Layer (SSL) connections without using a load balancer. This may reduce the complexity and cost [ELB-pricing] of a Concourse deployment.
0. Pre-requisites
Deploy Concourse with BOSH. Follow the instructions here.
1. Upload nginx BOSH Release
Next, upload the nginx BOSH release to your director (note that we’re using an experimental CLI whose syntax is slightly different [Golang CLI] ):
bosh upload-release https://github.com/cloudfoundry-community/nginx-release/releases/download/v4/nginx-4.tgz
2. Add nginx to your BOSH manifest
Add the release:
releases:
- name: nginx
version: latest
Add the nginx job properties.
The following example shows the manifest properties for https://ci.nono.io. Be sure to replace all occurrences of “ci.nono.io” with the fully qualified domain name (FQDN) of your Concourse server. Also, substitute the appropriate SSL certificate(s) and key.
- name: nginx
release: nginx
properties:
nginx_conf: |
worker_processes 1;
error_log /var/vcap/sys/log/nginx/error.log info;
events {
worker_connections 1024;
}
http {
include /var/vcap/packages/nginx/conf/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server_names_hash_bucket_size 64;
# redirect HTTP to HTTPS
server {
server_name _; # invalid value which will never trigger on a real hostname.
listen 80;
rewrite ^ https://ci.nono.io$request_uri?;
access_log /var/vcap/sys/log/nginx/ci.nono.io-access.log;
error_log /var/vcap/sys/log/nginx/ci.nono.io-error.log;
}
server {
server_name ci.nono.io;
# weak DH https://weakdh.org/sysadmin.html
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
# poodle https://scotthelme.co.uk/sslv3-goes-to-the-dogs-poodle-kills-off-protocol/
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
listen 443 ssl;
ssl_certificate /var/vcap/jobs/nginx/etc/ssl_chained.crt.pem;
ssl_certificate_key /var/vcap/jobs/nginx/etc/ssl.key.pem;
access_log /var/vcap/sys/log/nginx/ci.nono.io-access.log;
error_log /var/vcap/sys/log/nginx/ci.nono.io-error.log;
root /var/vcap/jobs/nginx/www/document_root;
index index.shtml index.html index.htm;
# https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-with-ssl-as-a-reverse-proxy-for-jenkins
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Fix `websocket: bad handshake` when using `fly intercept`
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Fix the “It appears that your reverse proxy set up is broken" error.
proxy_pass http://localhost:8080;
proxy_read_timeout 90;
proxy_redirect http://localhost:8080 https://ci.nono.io;
}
}
}
# FIXME: replace with your HTTPS SSL key
ssl_key: ((nono_io_key))
# FIXME: replace with your HTTPS SSL chained certificate
ssl_chained_cert: |
-----BEGIN CERTIFICATE-----
MIIFXDCCBESgAwIBAgIQOvRHkhKyb/k9O4xvIi9zZTANBgkqhkiG9w0BAQsFADCB
...
NaaNSyS8pHUJhaq+ZiC7zM2YsuLBICPQfsunHGrho4k=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
...
+AZxAeKCINT+b72x
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
...
pu/xO28QOG8=
-----END CERTIFICATE-----
3. BOSH Deploy
We deploy (note that we are using an experimental BOSH CLI whose syntax differs slightly from the canonical Ruby CLI’s):
bosh deploy -d concourse concourse.yml -l <(lpass show --note deployments)
We browse to our newly-deployed Concourse server to verify an SSL connection: https://ci.nono.io
4. Manifests
- Concourse server + nginx front-end BOSH manifest
- BOSH Director Cloud Config manifest
- BOSH Director manifest [Google Cloud]
Addendum: Using Concourse’s Built-in TLS instead of nginx
Concourse has BOSH job properties that allow you to set the TLS key and certificate, bypassing the need for a colocated nginx job:
instance_groups:
jobs:
atc:
properties:
tls_cert: |
-----BEGIN CERTIFICATE-----
MIIFXDCCBESgAwIBAgIQOvRHkhKyb/k9O4xvIi9zZTANBgkqhkiG9w0BAQsFADCB
...
NaaNSyS8pHUJhaq+ZiC7zM2YsuLBICPQfsunHGrho4k=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
....
pu/xO28QOG8=
-----END CERTIFICATE-----
tls_key: |
-----BEGIN RSA PRIVATE KEY-----
This simple solution has a downside: the Concourse URI’s requires a port number at the end (e.g. the URI would be https://ci.nono.io:4443 instead of https://ci.nono.io). [privileged]
Here is a sample BOSH manifest which uses Concourse’s built-in TLS.
Footnotes
[ELB-pricing] ELB pricing, as of this writing, is $0.025/hour, $0.60/day, $219.15 / year (assuming 365.24 days / year).
Load balancers (specifically ELBs) offer the ability to direct traffic to a pool of backend servers and to automatically remove a server from the pool if it becomes unresponsive; however, this feature is often unneeded — we are unaware of any Concourse server deployments which have multiple backends. A load balancer in front of a Concourse server is an unnecessary expense in our opinion.
[Golang CLI]
We are using an experimental Golang-based BOSH command line interface (CLI), and
the arguments are slightly different than those of canonical Ruby-based BOSH
CLI; however, the
arguments are similar enough to be readily adapted to the Ruby CLI (e.g. the
Golang CLI’s bosh upload-stemcell
equivalent to the Ruby CLI’s bosh upload stemcell
(no dash)).
The new CLI also allows variable interpolation, with the value of the variables to interpolate passed in via YAML file on the command line. This feature allows the redaction of sensitive values (e.g. SSL keys) from the manifest. The format is similar to Concourse’s interpolation, except that interpolated values are bracketed by double parentheses “((key))”, whereas Concourse uses double curly braces “{{key}}”.
Similar to Concourse, the experimental BOSH CLI allows the YAML file containing
the secrets to be passed via the command line, e.g. -l ~/secrets.yml
or
-l <(lpass show --note secrets)
The Golang CLI is in alpha and should not be used on production systems.
[Google Cloud]
We are deploying to Google Cloud Platform’s Google
Compute Engine (GCE), and thus the
cloud_properties
sections of the BOSH Director’s manifest may appear
unfamiliar to those who deploy on AWS or vSphere.
[privileged] Concourse will not bind to a privileged port.
You may attempt to force atc
to bind to port 443 by setting its
tls_bind_port
job property, but it will not work, atc
will not start, and you will see the
following message in /var/vcap/sys/log/atc/atc.stderr.log
:
web-tls exited with error: listen tcp 0.0.0.0:443: bind: permission denied