You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.6 KiB

gencert

This script generates x509 server certificate (with all IPs in SAN) signed by a self-signed CA.

Purpose

The whole idea is to run the gencert.sh script right before starting the service and then, making sure the reverse proxy updates its main CA bundle right before starting.

Some services require the certificate on their backend and would not be satisfied with the TLS termination elsewhere.

For example, one of such services is Minio. It requires its certificate in order to enable SSE-C (Server Side Encryption with Customer provided keys).

How does this script work

This script will always produce a self-signed x509 certificate with the IP addresses embedded to x509's SAN.

It will also produce a CA certificate and can be used by other services which may need to authenticate against this self-signed certificate.

The authentication works in a way that a public CA certificate will be used by the client in order to validate the server's certificate.

  • generate CA certificate if does not find any
  • always generate server certificate on startup to ensure all IP addresses are in x509 SAN
  • warn if the CA certificate is about to expire (<30 days till expiration)
  • regenerate the CA certificate if it finds it has expired

The CA certificate will be valid for 3650 days (10 years) The server certifcate will be valid for 365 days (1 year) The x509 certs are ECDSA with prime256v1 curve and SHA256 signatures

Examples

Minio and Docker Registry services behind Traefik reverse proxy

  • docker-compose.yml

I intentionally left only the gencert-related lines.

If you are using Alpine based image, the correct CA certificates path is /usr/local/share/ca-certificates/, otherwise one of these https://golang.org/src/crypto/x509/root_linux.go

services:
  minio:
    image: minio/minio
    volumes:
      - /srv/data/minio/certs:/root/.minio/certs
      - /srv/services/gencert/gencert.sh:/gencert.sh:ro
    entrypoint: sh -c "cd /root/.minio/certs &&
                       /gencert.sh --cn minio.example.com &&
                       minio server /data"
    environment:
      - "MINIO_ACCESS_KEY=redacted"
      - "MINIO_SECRET_KEY=redacted"

  registry:
    image: registry:2.6.2
    volumes:
      - /srv/services/gencert/gencert.sh:/gencert.sh:ro
      - /srv/data/registry/certs:/certs
    entrypoint: sh -c "cd /certs &&
                       /gencert.sh --cn registry.example.com &&
                       registry serve /etc/docker/registry/config.yml"
    environment:
      REGISTRY_HTTP_TLS_CERTIFICATE: '/certs/public.crt'
      REGISTRY_HTTP_TLS_KEY: '/certs/private.key'

  traefik:
    image: traefik:1.6-alpine
    volumes:
      - /srv/data/minio/certs/ca.crt:/usr/local/share/ca-certificates/minio_ca.crt:ro
      - /srv/data/registry/certs/ca.crt:/usr/local/share/ca-certificates/registry_ca.crt:ro
    command: sh -c "update-ca-certificates && traefik"
    depends_on:
      - minio
      - registry

helloworld with socat

socat could be handy when you need to see the flow between the client and the backend. Minimum socat version should be 1.7.3.2 so it will work with the ECDHE- OpenSSL ciphers.

services:
  helloworld:
    image: dockercloud/hello-world
    volumes:
      - /srv/services/gencert/gencert.sh:/gencert.sh:ro
    entrypoint: sh -c "mkdir /certs &&
                       cd /certs &&
                       /gencert.sh --cn helloworld &&
                       apk --update add socat &&
                       ( nohup /run.sh & ) &&
                       echo '@edge http://nl.alpinelinux.org/alpine/edge/main' | tee -a /etc/apk/repositories &&
                       apk --update add socat@edge &&
                       socat -v -v -d -d OPENSSL-LISTEN:443,reuseaddr,verify=0,cafile=./ca.crt,cert=./public.crt,key=./private.key,fork TCP4-CONNECT:127.0.0.1:80"
    labels:
      traefik.enable: 'true'
      traefik.frontend.rule: 'Host: hello.example.com'
      traefik.frontend.entryPoints: 'http,https'
      # traefik.port: '80'
      traefik.backend.loadbalancer.stickiness: 'true'
      traefik.port: '443'
      traefik.protocol: 'https'
      # traefik.frontend.passTLSCert: 'true'
      # traefik.frontend.passHostHeader: 'true'

Testing

I have added a simplistic script testme.sh that helps to test this script in the following Linux distributions:

  • Alpine 3.4
  • Alpine 3.7
  • Ubuntu Bionic
  • Debian Stretch
  • CentOS 7

Alpine 3.4 - as it has old getent which misses ahostsv4