/dev/oops

fiddyspence's blog

No Moar Apache

So, I’ve been contemplating an upgrade to puppetserver, and the thought occurred to me that now we’re not in a magical land of Passenger any more, why on earth should I need either Apache or nginx any more - so I had a crack at getting haproxy to do the SSL termination for me, because lightweight and why not.

First up, you need a recent version of haproxy that supports SSL termination. I nicked the process for creating some RPMs from here - someone’s blog. I used haproxy-1.5.12 on EL 6.5, as it happens.

RPMs built and installed, config please - I added these options to the apposite sections of haproxy.cfg

global
    tune.ssl.default-dh-param 2048
    ssl-default-bind-options no-sslv3
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS

frontend puppetmaster
        bind 0.0.0.0:8140 ssl crt /etc/haproxy/certs/certs/ crl-file /etc/haproxy/certs/crl.pem ca-file /etc/haproxy/certs/ca.pem verify optional force-tlsv12
        acl ca_match path_reg ^/([^/]+/certificate.*)$
        default_backend puppetserver
        http-request set-header X-Client-Verify-Real  %[ssl_c_verify]
        http-request set-header X-Client-Verify NONE if !{ hdr_val(X-Client-Verify-Real) eq 0 }
        http-request set-header X-Client-Verify SUCCESS if { hdr_val(X-Client-Verify-Real) eq 0 }
        http-request set-header X-Client-DN     CN=%{+Q}[ssl_c_s_dn(cn)]
        http-request set-header X-SSL-Subject       CN=%{+Q}[ssl_c_s_dn(cn)]
        use_backend puppetserverca if ca_match

backend puppetserver
        balance static-rr
        option httpchk GET /puppet/v3/status/production?environment=production HTTP/1.0\r\nAccept:text/pson\r\n\r\n
        server localserver 127.0.0.1:8141 check
backend puppetserverca
        option httpchk GET /production/certificate_revocation_list/ca
        server capool a.b.c.d:8140 weight 1 maxconn 100 ssl verify none

The first thing to do in order to get the backend working is to give haproxy the PEM cert so it can listen to SSL/TLS properly.

bind 0.0.0.0:8140 ssl crt /etc/haproxy/certs/certs/ crl-file /etc/haproxy/certs/crl.pem ca-file /etc/haproxy/certs/ca.pem verify optional force-tlsv12

Here, I just catted the private key & cert into a single file and copied the crl + ca cert into the right place. You’ll be pleased to know that it actually reads the CRL, so cert revocation works properly - just update the target file in the same way you would Apache, and reload the CRL and you get failed authentication. #winner.

So - the thing that took longer here is to make sure we set the right client headers, and then also make sure that CA traffic is proxied to the right place (because you’ve split out the CA to a dedicated set of servers, haven’t you). So we end up with 2 backends and redirection based on the URI passed. That bit is easy.

The hard part was figuring out the haproxy way of setting the HTTP headers to reflect the authentication. This ends up being to set a temporary header (X-Client-Verify-Real) that we can then compare to a value and set in a manner similar to Apache using an ACL.

http-request set-header X-Client-Verify-Real  %[ssl_c_verify]
http-request set-header X-Client-Verify NONE if !{ hdr_val(X-Client-Verify-Real) eq 0 }
http-request set-header X-Client-Verify SUCCESS if { hdr_val(X-Client-Verify-Real) eq 0 }

This way we get the right headers set for puppet to do its thing, and it all works and we don’t have the overhead of a large apache sat in the way. Your mileage may vary etc.

To Do:

I looked at passing through the PEM cert as X-Client-Cert but this requires modification to either haproxy or Puppet because right now I can only get it through as DER so it needs munging somewhere. Given that Apache is broken on this score currently, I didn’t see this as a deal breaker right now - trusted facts don’t work, but hey ho. I stuck a feature request in with the haproxy project to give the PEM cert - watch this space I guess.