FrankTheDevop FrankTheDevop FrankTheDevop FrankTheDevop FrankTheDevop

Digital Ocean

SSL Termination Stack Setup: Let´s encrypt, HAProxy, Your Stack

150 150 admin

Hi everyone,

for a setup at work I needed an quick and easy way to terminate an SSL Connection without hassle. After a short research I found it feasible to use Let´s encrypt for free SSL Certificates.But it looked like a lot of work to create the certificate so I searched for an quicker and hassle free approach. I found one, but it still took me a few hours to figure out how to use it correctly. And I want to save you the time.
My setup looked like this:
– Domain hosted at GoDaddy.com
– Server hosted at Digital Ocean (digitalocean.com*)
– Docker in Swarm Mode
– Portainer as UI
The expected outcome is:
– 1 Stack to (re-)generate the certificate
– 1-x Worker Stacks
If your Website is hosted somewhere else than GoDaddy that is no problem as long as you find it in this list: https://github.com/Neilpang/acme.sh/blob/master/dnsapi/README.md.
Let’s dive into the work:
1. Create API Credentials for GoDaddy / your supported Provider. How you do it depends on the provider, refer to this list: https://github.com/Neilpang/acme.sh/blob/master/dnsapi/README.md.
Remember to create Production keys, e.g. GoDaddy allows to create sandbox keys, those won’t work.
2. Deploy this stack config for the generation stack:
version: '3.5'
services:
  acme:
    command: daemon
    deploy:
      placement:
        constraints:
          - node.role == manager
      resources:
        reservations:
          cpus: '0.01'
          memory: 50M
    environment:
      DEPLOY_HAPROXY_PEM_PATH: /haproxy
      DEPLOY_HAPROXY_RELOAD: for task in $$(docker service ps SSL_system_haproxy -f desired-state=running -q); do docker run --rm -v /var/run/docker.sock:/var/run/docker.sock datagridsys/skopos-plugin-swarm-exec task-exec $$task /reload.sh; done
      
    hostname: '{{ .Service.Name }}-{{ .Task.Slot }}'
    image: interaction/acme.sh
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - acme-data:/acme.sh
      - nginx-data:/www
      - system_haproxy-data:/haproxy
  nginx:
    deploy:
      resources:
        reservations:
          cpus: '0.01'
          memory: 20M
    healthcheck:
      test: curl -f http://localhost || exit 1
    hostname: '{{ .Service.Name }}-{{ .Task.Slot }}'
    image: interaction/acme.sh-nginx
    ports:
      - 80:80
    volumes:
      - nginx-data:/www

volumes:
  acme-data:
    driver: local
    name: 'acme-data'
  nginx-data:
    driver: local
    name: 'nginx-data'
  system_haproxy-data:
    external: true

 

3. Go into your acme Container, either by docker exec -it $containerhash /bin/sh or via your UI.
4. For GoDaddy issue the commands:
4.1 export GD_Key=$yourkey
4.2 export GD_Secret=$yoursecret
4.3 acme.sh —issue -d “$yourFQDN” —dns dns_gd —dnssleep 15
4.4 acme.sh —deploy -d “$yourFQDN” —deploy-hook haproxy
The first two Commands 4.2 and 4.2 will set the required environment variables for the acme.sh script. In 4.3 replace $yourFQDN with the (sub-)domain you want the certificate be created for, e.g. web.stack.example.com.
With the last Command 4.4 you deploy the Certificate and let the script restart your HAProxy.
Let look at your worker stack. Here is my definition:
version: '3.5'
services:
  system_haproxy:
    image: 'dockercloud/haproxy:1.6.6'
    depends_on:
      - web
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M
    environment:
      - CERT_FOLDER=/haproxy
      - DOCKER_HOST=127.0.0.1
      - 'EXTRA_GLOBAL_SETTINGS="debug"'
      - 'STATS_AUTH=admin:$password'
      - 'STATS_PORT=1936'
      - DOCKER_TLS_VERIFY
      - DOCKER_HOST
      - DOCKER_CERT_PATH
    volumes:
      - system_haproxy-data:/haproxy
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - '443:443'
      - '1936:1936'
      
  web:
    image: 'dockercloud/hello-world:v1.0.0-frank'
    hostname: '{{ .Service.Name }}-{{ .Task.Slot }}'
    environment:
      - SERVICE_PORTS=$yourport
      - FORCE_SSL=yes
      - SSL_CERT=/haproxy/$crtificatename.pem
      - 'VIRTUAL_HOST=https://$yourFQDN'

volumes:
  system_haproxy-data:
    external: true

 

So what do we have here? A HAProxy Container with my default configuration + SSL Ports and the ENV Var CERT_FOLDER pointing to the folder where the certificate(s) are located. That is needed for the start up as HAProxy recognises, that you want SSL Termination and requires one of multiple ways (for more details see https://github.com/docker/dockercloud-haproxy/tree/master#ssl-termination).
The second entry is a test container you can find on docker hub, I just changed it to another port to reflect my own requirements. Normally the image is ‘dockercloud/hello-world’.
The important things here are the Environment Variables. VIRTUAL_HOST is probably already known by you. You can set the scheme to https instead of http and HAProxy recognises it. You also need the SERVICE_PORTS set to the ports you want to use on this container.
What is probably new to you is FORCE_SSL and SSL_CERT. FORCE_SSL enforces that every access to this container will be done securely via HTTPS. And SSL_CERT points to the location of the certificate we generated earlier. It is the mount point of the external container that is shared with the acme container.
After you deployed both stacks and issued the four commands in the acme container you are ready to go. When you open $yourFQN in your browser you should see something similar to this picture:
Congratulations! You now have a SSL terminated Stack you can easily develop and have no dependencies inside your worker stack(s). I hope I could save you quite some time so you can enjoy the benefits!
You can find the two stack definitions here: https://github.com/FrankTheDevop/ssl-termination-stack.
Feel free to use them :).
Kind Regards,
Frank
* Affiliate Link