Skip to content

Latest commit

 

History

History
176 lines (136 loc) · 5.96 KB

README.md

File metadata and controls

176 lines (136 loc) · 5.96 KB

Certyman

A convoluted way to get certificates with Docker via Let's Encrypt.

Purpose

This is yet a another free certificate manager based around Docker. The main thing this does differently is that the certificates are not stored in a file, but rather a Redis instance. This allows you to run multiple instances of Nginx/equivalent and serve the same certificate without requesting new ones on the first request.

Certyman uses just Docker with the help of labeling, with the downside that it's not the most friendly thing to work with since you don't have a framework behind you. There are no releases for Certyman since this was more of an experiment, so if you want to try it out you'll need to build it yourself.

How it works

  1. Start a docker web server with certyman.domain set to the domain oyu want
  2. docker-event-emitter with pick up the event, and send it to Redis
  3. Certyman-listener to receive the event and grab the certyman.domain domain along with other networking information Docker provides and put that data into Redis.
  4. (In my case) Openresty with search Redis for the list of domains to be served, and generate certificates if there are none. Certificates are stored in Redis as well.

Usage

  • You'll need to run docker-event-emitter on each of your docker hosts.
  • Have a redis instance running.
  • Run one certyman-listener with access to Redis
  • (Optional) Run certyman-ui for a web page to view the list of domains served.

Once the above are running, all you will need to do is start your Docker containers with the certyman.domain set to the domain you wish to route to with TLS (note you cannot use wildcards with Let's Encrypt).

Using Openresty, you can run something like the following to get your certificates served (see lua-resty-auto-ssl):

events {
    worker_connections 1024;
}

http {
    # The "auto_ssl" shared dict must be defined with enough storage space to
    # hold your certificate data.
    lua_shared_dict auto_ssl 1m;

    # A DNS resolver must be defined for OSCP stapling to function.
    resolver 8.8.8.8;

    # Initial setup tasks.
    init_by_lua_block {
        auto_ssl = (require "resty.auto-ssl").new()

        -- Define a function to determine which SNI domains to automatically handle
        -- and register new certificates for. Defaults to not allowing any domains,
        -- so this must be configured.
        auto_ssl:set("allow_domain", function(domain)
            local resty_redis = require "resty.redis"
            local redis = resty_redis:new()
            local log = ngx.log
            local ERR = ngx.ERR

            redis:set_timeout(10000)

            local ok, err = redis:connect("172.16.238.15", 6379)
            if not ok then
                log(ERR, "Error connecting to redis: " .. err)
                redis:set_keepalive(1000, 100)
                return false
            end

            local is_member, err = redis:sismember("lemanager:domains", domain)
            if err then
                log(ERR, "Error checkout if " .. domain .. " is in lemanager:domains: " .. err)
                redis:set_keepalive(1000, 100)
                return false
            end

            log(ERR, is_member ~= 0)
            redis:set_keepalive(10000, 100)
            return is_member ~= 0
        end)

        auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis")

        auto_ssl:set("redis", {
            host = "172.16.238.15",
            port = 6379
        })

        auto_ssl:init()
    }

    init_worker_by_lua_block {
        auto_ssl:init_worker()
    }

    # admin
    server {
        listen 443 ssl;
        server_name <domain for certy-ui>;
        allow 58.28.155.208;

        ssl_certificate_by_lua_block {
            auto_ssl:ssl_certificate()
        }

        ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt;
        ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key;

        location / {
            proxy_pass http://ui:8001;
        }
    }

    # HTTPS server
    server {
        listen 443 ssl;
        server_name _;

        ssl_certificate_by_lua_block {
            auto_ssl:ssl_certificate()
        }

        ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt;
        ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key;

        location / {
            set $new_host '';

            access_by_lua_block {
                local resty_redis = require "resty.redis"
                local redis = resty_redis:new()
                local log = ngx.log
                local ERR = ngx.ERR

                redis:set_timeout(10000)

                local ok, err = redis:connect("172.16.238.15", 6379)
                if not ok then
                    log(ERR, "Error connecting to redis: " .. err)
                    redis:set_keepalive(10000, 100)
                    return
                end
                
                log(ERR, ngx.var.host)
                local new_host, err = redis:smembers("lemanager:domains:" .. ngx.var.host)
                if err then
                    log(ERR, "Error find upstream")
                    redis:set_keepalive(10000, 100)
                    return
                end

                redis:set_keepalive(10000, 100)
                ngx.var.new_host = "http://" .. new_host[1]
            }

            proxy_pass $new_host;
        }
    }

    # HTTP server
    server {
        listen 80;

        # Endpoint used for performing domain verification with Let's Encrypt.
        location /.well-known/acme-challenge/ {
            content_by_lua_block {
                auto_ssl:challenge_server()
            }
        }
    }

    # Internal server running on port 8999 for handling certificate tasks.
    server {
        listen 127.0.0.1:8999;
        location / {
            content_by_lua_block {
                auto_ssl:hook_server()
            }
        }
    }
}