Skip to content

Commit

Permalink
Merge pull request #1 from Energy1190/pr
Browse files Browse the repository at this point in the history
 Merge certificates with a common second-level domain name into a single certificate with multiple sub-names
  • Loading branch information
dmitryint authored May 16, 2019
2 parents 718205d + 11d565b commit 74385e7
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 5 deletions.
43 changes: 41 additions & 2 deletions lib/resty/auto-ssl/ssl_certificate.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ local function issue_cert_unlock(domain, storage, local_lock, distributed_lock_v
end

local function issue_cert(auto_ssl_instance, storage, domain)
local fullchain_pem, privkey_pem, err
local multiname = auto_ssl_instance:get("multiname_cert")
-- Before issuing a cert, create a local lock to ensure multiple workers
-- don't simultaneously try to register the same cert.
local local_lock, new_local_lock_err = lock:new("auto_ssl", { exptime = 30, timeout = 30 })
Expand Down Expand Up @@ -90,7 +92,15 @@ local function issue_cert(auto_ssl_instance, storage, domain)
issue_cert_unlock(domain, storage, local_lock, distributed_lock_value)
return cert
end


if not multiname then
fullchain_pem, privkey_pem = storage:get_cert(domain)
if fullchain_pem and privkey_pem then
issue_cert_unlock(domain, storage, local_lock, distributed_lock_value)
return fullchain_pem, privkey_pem
end
end

ngx.log(ngx.NOTICE, "auto-ssl: issuing new certificate for ", domain)
cert, err = ssl_provider.issue_cert(auto_ssl_instance, domain)
if err then
Expand Down Expand Up @@ -277,6 +287,35 @@ local function do_ssl(auto_ssl_instance, ssl_options)
return
end

-- Check to ensure the domain is one we allow for handling SSL.
local allow_domain = auto_ssl_instance:get("allow_domain")
if not allow_domain(domain) then
ngx.log(ngx.NOTICE, "auto-ssl: domain not allowed - using fallback - ", domain)
return
end

local multiname = auto_ssl_instance:get("multiname_cert")
if multiname then
local storage = auto_ssl_instance:get("storage")
domain, sub_domain = storage:get_domains(domain, multiname)
local check_subdomain, size = storage:check_subdomain(domain, sub_domain)
if size then
if size>99 then
storage:set_subdomain(domain, sub_domain, sub_domain)
storage:set_subdomain(sub_domain, sub_domain)
elseif not check_subdomain then
storage:set_subdomain(domain, sub_domain, nil)
issue_cert(auto_ssl_instance, storage, domain)
end
elseif not check_subdomain then
storage:set_subdomain(domain, sub_domain, nil)
issue_cert(auto_ssl_instance, storage, domain)
end

local check_subdomain, size = storage:check_subdomain(domain, sub_domain)
domain = check_subdomain
end

-- Get or issue the certificate for this domain.
local cert_der, get_cert_der_err = get_cert_der(auto_ssl_instance, domain, ssl_options)
if get_cert_der_err then
Expand Down Expand Up @@ -304,4 +343,4 @@ return function(auto_ssl_instance, ssl_options)
if not ok then
ngx.log(ngx.ERR, "auto-ssl: failed to run do_ssl: ", err)
end
end
end
21 changes: 19 additions & 2 deletions lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,30 @@ function _M.issue_cert(auto_ssl_instance, domain)
local base_dir = auto_ssl_instance:get("dir")
assert(type(base_dir) == "string", "dir must be a string")

local hook_port = auto_ssl_instance:get("hook_server_port")
local multiname = auto_ssl_instance:get("multiname_cert")
domains = "--domain " .. domain .. " "

local hook_port = auto_ssl_instance:get("hook_server_port")
assert(type(hook_port) == "number", "hook_port must be a number")
assert(hook_port <= 65535, "hook_port must be below 65536")

local hook_secret = ngx.shared.auto_ssl_settings:get("hook_server:secret")
assert(type(hook_secret) == "string", "hook_server:secret must be a string")

if multiname then
local storage = auto_ssl_instance:get("storage")
domain_list, size = storage:get_subdomain(domain)
domains = " "
if domain_list then
for _, i in pairs(domain_list) do
domains = domains .. "--domain " .. i .. " "
end
else
domains = "--domain " .. domain .. " "
end
end

-- Run dehydrated for this domain, using our custom hooks to handle the
-- domain validation and the issued certificates.
--
Expand All @@ -31,7 +48,7 @@ function _M.issue_cert(auto_ssl_instance, domain)
"--cron",
"--accept-terms",
"--no-lock",
"--domain", domain,
domains,
"--challenge", "http-01",
"--config", base_dir .. "/letsencrypt/config",
"--hook", lua_root .. "/bin/resty-auto-ssl/letsencrypt_hooks",
Expand Down Expand Up @@ -93,4 +110,4 @@ function _M.issue_cert(auto_ssl_instance, domain)
return cert
end

return _M
return _M
178 changes: 177 additions & 1 deletion lib/resty/auto-ssl/storage.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
local resty_random = require "resty.random"
local str = require "resty.string"
local cjson = require "cjson"

local _M = {}


function _M.new(options)
assert(options)
assert(options["adapter"])
Expand All @@ -11,6 +13,180 @@ function _M.new(options)
return setmetatable(options, { __index = _M })
end

function tablelength(a)
local count = 0
for _ in pairs(a) do count = count + 1 end
return count
end

function _M.get_domains(self, domain, level)
local function subdomain(a)
local x = {}
for word in string.gmatch(a, '([^.]+)') do
table.insert(x, word)
end
return x
end

local function get_name(name_list, size, level)
if level > size then
return nil
end
x = name_list[size]
for i=1, level-1 do
x = name_list[size-i] .. "." .. x
end
return x
end

if type(level) ~= "number" then
level = 2
end
local ar = subdomain(domain)
local size = tablelength(ar)

local main_domain = get_name(ar, size, level)
if main_domain then
return main_domain, domain
else
return domain, nil
end
end

function _M.get_subdomain(self, domain)
local function subdomains(a)
local x = {}
if a then
for word in string.gmatch(a, '([^:]+)') do
table.insert(x, word)
end
return x
end
return nil
end

local function check_max_len(subdomain_list, size)
local x = ((string.len(table.concat(subdomain_list, ":"))) * size) + (10 * size)
if x > 1000 then
return 100
else
return size
end
end

local json, err = self.adapter:get(domain .. ":main")
if err then
return nil, nil, err
elseif not json then
return nil
end
local data = cjson.decode(json)
local ar = subdomains(data['subdomain'])
local extended = subdomains(data['extended'])
local size = check_max_len(ar, tablelength(ar))
return ar, size, nil, extended
end

function _M.set_subdomain(self, domain, subdomain, extended)
local x, n, err, extended_list = self.get_subdomain(self, domain)

local function check_extended(extended, extended_list)
local x = nil
if extended_list then
x = table.concat(extended_list, ":") .. ":"
end
if extended then
if x then
x = x .. extended
else
x = extended
end
end
return x
end

local function set_subdomains(subdomain, subdomain_list, err, extend)
local function check_name(subdomain, subdomain_list)
for _, i in pairs(subdomain_list) do
if i == subdomain then
return true
end
end
end

if err then
return subdomain
end

local x = table.concat(subdomain_list, ":")
if check_name(subdomain, subdomain_list) then
return nil, true
elseif nil == string.find(x, subdomain) and extend then
return x
elseif nil == string.find(x, subdomain) then
x = x .. ":" .. subdomain
return x
end
end

local extend = check_extended(extended, extended_list)
local subdomain_list, exists = set_subdomains(subdomain, x, err, extend)
if exists then
return
end
if extend then
data = cjson.encode({domain=domain,
subdomain=subdomain_list,
extended=extend})
else
data = cjson.encode({domain=domain,
subdomain=subdomain_list})
end
self.adapter:set(domain .. ":main", data)
end

function _M.check_subdomain(self, domain, subdomain)
local x, n, err, extended = self.get_subdomain(self, domain)

local function check_main(domain_list, subdomain)
if domain_list then
local size = tablelength(domain_list)
for _, i in pairs(domain_list) do
if i == subdomain then
return domain, size
end
end
end
end

local function check_extended(self, extended_list, subdomain)
if extended_list then
for _, i in pairs(extended_list) do
domain, size = self.check_subdomain(self, i, subdomain)
if domain then
return domain, size
end
end
end
end

local domain, size = check_main(x, subdomain)
if domain then
return domain, size
end

local domain, size = check_extended(self, extended, subdomain)
if domain then
return domain, size
end

if n and n>99 then
return nil, n
end

return nil, nil
end

function _M.get_challenge(self, domain, path)
return self.adapter:get(domain .. ":challenge:" .. path)
end
Expand Down Expand Up @@ -132,4 +308,4 @@ function _M.issue_cert_unlock(self, domain, lock_rand_value)
end
end

return _M
return _M

0 comments on commit 74385e7

Please sign in to comment.