diff --git a/lib/resty/auto-ssl/ssl_certificate.lua b/lib/resty/auto-ssl/ssl_certificate.lua index 70d3a78..9ccbd53 100644 --- a/lib/resty/auto-ssl/ssl_certificate.lua +++ b/lib/resty/auto-ssl/ssl_certificate.lua @@ -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 }) @@ -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 @@ -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 @@ -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 \ No newline at end of file diff --git a/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua b/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua index 680b94a..6048db2 100644 --- a/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua +++ b/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua @@ -11,6 +11,10 @@ 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") @@ -18,6 +22,19 @@ function _M.issue_cert(auto_ssl_instance, domain) 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. -- @@ -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", @@ -93,4 +110,4 @@ function _M.issue_cert(auto_ssl_instance, domain) return cert end -return _M +return _M \ No newline at end of file diff --git a/lib/resty/auto-ssl/storage.lua b/lib/resty/auto-ssl/storage.lua index 0f18f35..cabe119 100644 --- a/lib/resty/auto-ssl/storage.lua +++ b/lib/resty/auto-ssl/storage.lua @@ -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"]) @@ -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 @@ -132,4 +308,4 @@ function _M.issue_cert_unlock(self, domain, lock_rand_value) end end -return _M +return _M \ No newline at end of file