Skip to content

Instantly share code, notes, and snippets.

@gladiatr72
Created April 26, 2016 20:09
Show Gist options
  • Save gladiatr72/5651b25b04fdfa218c67ef89c7369546 to your computer and use it in GitHub Desktop.
Save gladiatr72/5651b25b04fdfa218c67ef89c7369546 to your computer and use it in GitHub Desktop.
A Centralized Collector Config for Letsencrypt using NGiNX

Purpose

The purpose of this configuration is to allow the letsencrypt-auto script to function properly from a centralized configuration management host. This allows for managing/automating the creation, deployment and renewal of certificates without resorting to retrieving trust related data from the less trusted managed hosts.

An added bonus is that the auth data stored with the renewal configuration is not littered across the enterprise.

IP Addresses or Host Names

This particular collector configuration is contingent upon the server certificate having the host IP addressses in its Subject Alternative Name list. Is it neccessary? That is a question that you must answer for yourself. Internal and external addresses are readily available by way of our automation infrastructure--one less dependency to worry about.

Why SSL Between the Web Server and the Collector?

The request contents itself is not particularly sensitive--the connection from letsencrypt comes in on port 80!

We have an existing, internal CA infrastructure that automatically generates and deploys system-specific server and client certs. The encryption is useful when connecting to databases or API endpoints; however, in this case, the most important feature is the authentication of the connection. Both local and remote managed systems can proxy connections back to the collector.

internally generated certificate example

# openssl x509 -in /etc/ssl/certs/collector_server.crt -noout -text

[...]
        X509v3 extensions:
            X509v3 Key Usage:
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Subject Alternative Name:
                DNS:mc-01.salt.revsys.000.prod.ec2.eu-c-2, DNS:52.27.33.198, IP Address:52.27.33.198, DNS:10.64.8.39, IP Address:10.64.8.39, 
            X509v3 Subject Key Identifier:
[...]

For those that are interested: You would think that specifying an IP address in the SAN list would be adequate. I thought thusly. Verily, I was wrong. Some client libraries soil themselves if the target for the SSL request is not listed as a DNS element. Other client libraries work as expected using the IP and IPv6 categories.

The Collector Config

server {
    listen 49152 ssl;

    server_name 10.64.8.39;
    server_name 52.27.33.198;

    access_log /var/log/nginx/letsencrypt_access.log;
    error_log /var/log/nginx/letsencrypt_error.log debug;

    location ~ "^/.well-known/acme-challenge/([\w_-]{43})$" {
        alias /var/www/nginx/le/.well-known/acme-challenge/$1;
    }
   
    include /etc/nginx/mime.types;
    include /etc/nginx/ssl.conf;

    ssl_certificate /etc/ssl/certs/<your.server.cert.here>;
    ssl_certificate_key /etc/ssl/private/<your.server.rsa.key.here>;
    ssl_verify_client on;
    ssl_client_certificate /etc/ssl/certs/<cert for CA that signs your client certificates>;
    ssl_crl /etc/ssl/certs/<signing CA crl>;

    access_log /var/log/nginx/letsencrypt_access.log;
    error_log /var/log/nginx/letsencrypt_error.log;

    proxy_set_header        Host            $host;
    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    client_max_body_size    10m;
    client_body_buffer_size 128k;
    proxy_connect_timeout   90;
    proxy_send_timeout      90;
    proxy_read_timeout      90;
    proxy_buffers           16 100k;
}

Why use a High Port?

We have other web things running on the collector--they all use app-specific ssl certificates and leverage NGiNX's SNI functionality. SNI gets a bit gummed up if you throw IP addresses as server names at it. If you don't find yourself in this sort of situation, using the standard port works just as well.

The Managed Server Config

server {
  listen 80;
  
  error_log /var/log/nginx/sites_error.log;
  server_name www.something.revsys.com;

    location ~ "^/(.well-known/acme-challenge/[\w_-]{43})$" {
        access_log /var/log/nginx/le_something.log;

        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;

        client_max_body_size    10m;
        client_body_buffer_size 128k;
        proxy_connect_timeout   90;
        proxy_send_timeout      90;
        proxy_read_timeout      90;
        proxy_buffers           16 100k;
        proxy_redirect off;

        proxy_ssl_trusted_certificate /etc/ssl/certs/revsys_root/ca.crt;
        proxy_ssl_certificate /etc/ssl/client/revsys_root/salt-minion-X.crt
        proxy_ssl_certificate_key /etc/ssl/private/salt-minion-X.key

        proxy_ssl_verify on;
        proxy_ssl_verify_depth 2;

        proxy_pass https://10.64.8.39:49152;
    }
    
    location / {
        [...];
    }
}

This can be used by itself as an initial nginx service deployment configuration. Once a letsencrypt certificate is available, the actual site SSL configuration bits can be appended to the server block. Since we already have an existing CA in place, our initial deployments include internal site-specific certificates that are replaced during subsequent runs.

This mechanism can be easily tested by creating a file on the collector: echo 'whee!' > /var/www/nginx/le/.well-known/acme-challenge/0000000000000000000000000000000000000000 (<- 44 zeros)

From a remote system:

$ curl http://www.mysystem.domain/.well-known/acme-challenge/0000000000000000000000000000000000000000000
whee!

Once this is in place, from the collector:

# /opt/letsencrypt/letsencrypt-auto certonly --webroot  -w /var/www/nginx/le -d www.something.revsys.com
Checking for new version...
Requesting root privileges to run letsencrypt...
   /root/.local/share/letsencrypt/bin/letsencrypt certonly --webroot -w /var/www/nginx/le -d www.something.revsys.com

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/www.something.revsys.com/fullchain.pem. Your
   cert will expire on 2016-07-25. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https

If you maintain the letsencrypto-auto storage paths, all of your shiny new certificates will appear in /etc/letsencrypt/live/<fqdn of all the things>.

Version Information:

program version
nginx 1.9.7
openssl 1.0.1f
letsencrypt 0.6.0.dev0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment