From 00f0a7623be7461d5a195cb5c45a1f9792e3a371 Mon Sep 17 00:00:00 2001 From: Energy1190 Date: Mon, 3 Apr 2017 19:12:23 +0300 Subject: [PATCH 1/2] Merge certificates with a common second-level domain name into a single certificate with multiple sub-names. --- lib/resty/auto-ssl/ssl_certificate.lua | 9 +- .../auto-ssl/ssl_providers/lets_encrypt.lua | 72 ++++++++---- lib/resty/auto-ssl/storage.lua | 103 +++++++++++++++++- 3 files changed, 157 insertions(+), 27 deletions(-) diff --git a/lib/resty/auto-ssl/ssl_certificate.lua b/lib/resty/auto-ssl/ssl_certificate.lua index 2022869..dfab0ea 100644 --- a/lib/resty/auto-ssl/ssl_certificate.lua +++ b/lib/resty/auto-ssl/ssl_certificate.lua @@ -83,7 +83,10 @@ local function issue_cert(auto_ssl_instance, storage, domain) end ngx.log(ngx.NOTICE, "auto-ssl: issuing new certificate for ", domain) - fullchain_pem, privkey_pem, err = ssl_provider.issue_cert(auto_ssl_instance, domain) + local storage = auto_ssl_instance:get("storage") + local d, s = storage:get_domains(domain) + storage:set_subdomains(d, s) + fullchain_pem, privkey_pem, err = ssl_provider.issue_cert(auto_ssl_instance, domain) if err then ngx.log(ngx.ERR, "auto-ssl: issuing new certificate failed: ", err) end @@ -207,7 +210,9 @@ local function set_cert(auto_ssl_instance, domain, fullchain_der, privkey_der, n end -- Set OCSP stapling. - ok, err = set_ocsp_stapling(domain, fullchain_der, newly_issued) + local storage = auto_ssl_instance:get("storage") + local d, s = storage:get_domains(domain) + ok, err = set_ocsp_stapling(d, fullchain_der, newly_issued) if not ok then ngx.log(auto_ssl_instance:get("ocsp_stapling_error_level"), "auto-ssl: failed to set ocsp stapling for ", domain, " - continuing anyway - ", err) end diff --git a/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua b/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua index 46bf3c5..e58dd19 100644 --- a/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua +++ b/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua @@ -14,16 +14,35 @@ function _M.issue_cert(auto_ssl_instance, domain) "env HOOK_SECRET=" .. ngx.shared.auto_ssl:get("hook_server:secret") .. " " .. "HOOK_SERVER_PORT=" .. hook_port + -- The result of running that command should result in the certs being + -- populated in our storage (due to the deploy_cert hook triggering). + local storage = auto_ssl_instance:get("storage") + local fullchain_pem, privkey_pem = storage:get_cert(domain) + -- Run dehydrated for this domain, using our custom hooks to handle the -- domain validation and the issued certificates. -- -- Disable dehydrated's locking, since we perform our own domain-specific -- locking using the storage adapter. + local dom, z = storage:get_domains(domain) + local d, zz = storage:get_subdomains(dom) + function get_domain_list(domain) + local str = '' + for _, i in pairs(domain) do + str = str .. "--domain " .. i .. " " + end + return str + end + if d then + domainus = get_domain_list(d) + else + domainus = "--domain " .. z .. " " + end local command = env_vars .. " " .. package_root .. "/auto-ssl/vendor/dehydrated " .. "--cron " .. "--no-lock " .. - "--domain " .. domain .. " " .. + domainus .. "--challenge http-01 " .. "--config " .. base_dir .. "/letsencrypt/config " .. "--hook " .. package_root .. "/auto-ssl/shell/letsencrypt_hooks" @@ -35,10 +54,6 @@ function _M.issue_cert(auto_ssl_instance, domain) ngx.log(ngx.DEBUG, "auto-ssl: dehydrated output: " .. out) - -- The result of running that command should result in the certs being - -- populated in our storage (due to the deploy_cert hook triggering). - local storage = auto_ssl_instance:get("storage") - local fullchain_pem, privkey_pem = storage:get_cert(domain) -- If dehydrated said it succeeded, but we still don't have any certs in -- storage, the issue is likely that the certs have been deleted out of our @@ -47,22 +62,39 @@ function _M.issue_cert(auto_ssl_instance, domain) -- storage with dehydrated's local copies. if not fullchain_pem or not privkey_pem then ngx.log(ngx.WARN, "auto-ssl: dehydrated succeeded, but certs still missing from storage - trying to manually copy - domain: " .. domain) - - command = env_vars .. " " .. - package_root .. "/auto-ssl/shell/letsencrypt_hooks " .. - "deploy_cert " .. - domain .. " " .. - base_dir .. "/letsencrypt/certs/" .. domain .. "/privkey.pem " .. - base_dir .. "/letsencrypt/certs/" .. domain .. "/cert.pem " .. - base_dir .. "/letsencrypt/certs/" .. domain .. "/fullchain.pem " .. - base_dir .. "/letsencrypt/certs/" .. domain .. "/chain.pem " .. - math.floor(ngx.now()) - status, out, err = shell_execute(command) - if status ~= 0 then - ngx.log(ngx.ERR, "auto-ssl: dehydrated manual hook.sh failed: ", command, " status: ", status, " out: ", out, " err: ", err) - return nil, nil, "dehydrated failure" + if d then + for _, i in pairs(d) do + command = env_vars .. " " .. + package_root .. "/auto-ssl/shell/letsencrypt_hooks " .. + "deploy_cert " .. + i .. " " .. + base_dir .. "/letsencrypt/certs/" .. i .. "/privkey.pem " .. + base_dir .. "/letsencrypt/certs/" .. i .. "/cert.pem " .. + base_dir .. "/letsencrypt/certs/" .. i .. "/fullchain.pem " .. + base_dir .. "/letsencrypt/certs/" .. i .. "/chain.pem " .. + math.floor(ngx.now()) + status, out, err = shell_execute(command) + if status ~= 0 then + ngx.log(ngx.ERR, "auto-ssl: dehydrated manual hook.sh failed: ", command, " status: ", status, " out: ", out, " err: ", err) + return nil, nil, "dehydrated failure" + end + end + else + command = env_vars .. " " .. + package_root .. "/auto-ssl/shell/letsencrypt_hooks " .. + "deploy_cert " .. + z .. " " .. + base_dir .. "/letsencrypt/certs/" .. z .. "/privkey.pem " .. + base_dir .. "/letsencrypt/certs/" .. z .. "/cert.pem " .. + base_dir .. "/letsencrypt/certs/" .. z .. "/fullchain.pem " .. + base_dir .. "/letsencrypt/certs/" .. z .. "/chain.pem " .. + math.floor(ngx.now()) + status, out, err = shell_execute(command) + if status ~= 0 then + ngx.log(ngx.ERR, "auto-ssl: dehydrated manual hook.sh failed: ", command, " status: ", status, " out: ", out, " err: ", err) + return nil, nil, "dehydrated failure" + end end - -- Try fetching again. fullchain_pem, privkey_pem = storage:get_cert(domain) end diff --git a/lib/resty/auto-ssl/storage.lua b/lib/resty/auto-ssl/storage.lua index cedeb52..a0e1dc5 100644 --- a/lib/resty/auto-ssl/storage.lua +++ b/lib/resty/auto-ssl/storage.lua @@ -9,6 +9,91 @@ function _M.new(adapter) return setmetatable({ adapter = adapter }, { __index = _M }) end +function _M.get_domains(self, domain) + function tablelength(a) + local count = 0 + for _ in pairs(a) do count = count + 1 end + return count + end + + function subdomain(a) + local x = {} + for word in string.gmatch(a, '([^.]+)') do + table.insert(x, word) + end + return x + end + + local ar = subdomain(domain) + local size = tablelength(ar) + local main_domain = ar[size-1] .. "." .. ar[size] + if size>2 then + return main_domain, domain + else + return main_domain, nil + end +end + +function _M.get_subdomains(self, domain) + function tablelength(a) + local count = 0 + for _ in pairs(a) do count = count + 1 end + return count + end + + function subdomains(a) + local x = {} + for word in string.gmatch(a, '([^:]+)') do + table.insert(x, word) + end + return x + 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 size = tablelength(ar) + return ar, size +end + +function _M.set_subdomains(self, domain, subdomain) + local x, n, err = self.get_subdomains(self, domain) + if err then + local data = cjson.encode({domain=domain, + subdomain=subdomain}) + self.adapter:set(domain .. "main", data) + else + for _, i in pairs(x) do + if i == subdomain then + return true + end + end + local sub = table.concat(x, ":") + if nil == string.find(sub, subdomain) then + local sub = sub .. ":" .. subdomain + local data = cjson.encode({domain=domain, + subdomain=sub}) + self.adapter:set(domain .. "main", data) + end + end +end + +function _M.check_subdomain(self, domain, subdomain) + local x, n, err = self.get_subdomains(self, domain) + if x then + for _, i in pairs(x) do + if i == subdomain then + return domain + end + end + end + return nil +end + function _M.get_challenge(self, domain, path) return self.adapter:get(domain .. ":challenge:" .. path) end @@ -22,7 +107,12 @@ function _M.delete_challenge(self, domain, path) end function _M.get_cert(self, domain) - local json, err = self.adapter:get(domain .. ":latest") + local d, z = self.get_domains(self, domain) + local c = self.check_subdomain(self, d, z) + if not c then + return nil + end + local json, err = self.adapter:get(d .. ":latest") if err then return nil, nil, err elseif not json then @@ -40,6 +130,7 @@ function _M.set_cert(self, domain, fullchain_pem, privkey_pem, cert_pem) -- a single string (regardless of implementation), and we don't have to worry -- about race conditions with the public cert and private key being stored -- separately and getting out of sync. + local d, z = self.get_domains(self, domain) local data = cjson.encode({ fullchain_pem = fullchain_pem, privkey_pem = privkey_pem, @@ -49,10 +140,10 @@ function _M.set_cert(self, domain, fullchain_pem, privkey_pem, cert_pem) -- Store the cert with the current timestamp, so the old certs are preserved -- in case something goes wrong. local time = ngx.now() * 1000 - self.adapter:set(domain .. ":" .. time, data) + self.adapter:set(d .. ":" .. time, data) -- Store the cert under the "latest" alias, which is what this app will use. - return self.adapter:set(domain .. ":latest", data) + return self.adapter:set(d .. ":latest", data) end function _M.all_cert_domains(self) @@ -82,7 +173,8 @@ end -- but in combination with resty-lock, it should prevent the vast majority of -- double requests. function _M.issue_cert_lock(self, domain) - local key = domain .. ":issue_cert_lock" + local d, z = self.get_domains(self, domain) + local key = d .. ":issue_cert_lock" local lock_rand_value = str.to_hex(resty_random.bytes(32)) -- Wait up to 30 seconds for any existing locks to be unlocked. @@ -110,7 +202,8 @@ function _M.issue_cert_lock(self, domain) end function _M.issue_cert_unlock(self, domain, lock_rand_value) - local key = domain .. ":issue_cert_lock" + local d, z = self.get_domains(self, domain) + local key = d .. ":issue_cert_lock" -- Remove the existing lock if it matches the expected value. local current_value, err = self.adapter:get(key) From 4914c164b67a4f994a80ad8d75684cbeeaaf5c00 Mon Sep 17 00:00:00 2001 From: Energy1190 Date: Fri, 14 Apr 2017 15:21:26 +0300 Subject: [PATCH 2/2] Correction of defects The main logic is moved to b. Added checks for the maximum length, for the maximum number of names in the certificate. The functional is now optional and is called by the parameter b. Through it, you can also transfer the desired domain level. --- lib/resty/auto-ssl/ssl_certificate.lua | 44 +++- .../auto-ssl/ssl_providers/lets_encrypt.lua | 86 +++----- lib/resty/auto-ssl/storage.lua | 206 ++++++++++++------ 3 files changed, 209 insertions(+), 127 deletions(-) diff --git a/lib/resty/auto-ssl/ssl_certificate.lua b/lib/resty/auto-ssl/ssl_certificate.lua index dfab0ea..adc422b 100644 --- a/lib/resty/auto-ssl/ssl_certificate.lua +++ b/lib/resty/auto-ssl/ssl_certificate.lua @@ -50,6 +50,7 @@ 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. @@ -76,17 +77,16 @@ local function issue_cert(auto_ssl_instance, storage, domain) -- After obtaining the local and distributed lock, see if the certificate -- has already been registered. - 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 + 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) - local storage = auto_ssl_instance:get("storage") - local d, s = storage:get_domains(domain) - storage:set_subdomains(d, s) - fullchain_pem, privkey_pem, err = ssl_provider.issue_cert(auto_ssl_instance, domain) + fullchain_pem, privkey_pem, err = ssl_provider.issue_cert(auto_ssl_instance, domain) if err then ngx.log(ngx.ERR, "auto-ssl: issuing new certificate failed: ", err) end @@ -210,9 +210,7 @@ local function set_cert(auto_ssl_instance, domain, fullchain_der, privkey_der, n end -- Set OCSP stapling. - local storage = auto_ssl_instance:get("storage") - local d, s = storage:get_domains(domain) - ok, err = set_ocsp_stapling(d, fullchain_der, newly_issued) + ok, err = set_ocsp_stapling(domain, fullchain_der, newly_issued) if not ok then ngx.log(auto_ssl_instance:get("ocsp_stapling_error_level"), "auto-ssl: failed to set ocsp stapling for ", domain, " - continuing anyway - ", err) end @@ -246,6 +244,28 @@ local function do_ssl(auto_ssl_instance, ssl_options) 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 fullchain_der, privkey_der, newly_issued, get_cert_err = get_cert(auto_ssl_instance, domain) if get_cert_err then @@ -269,4 +289,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 e58dd19..86c0d37 100644 --- a/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua +++ b/lib/resty/auto-ssl/ssl_providers/lets_encrypt.lua @@ -6,6 +6,8 @@ function _M.issue_cert(auto_ssl_instance, domain) local package_root = auto_ssl_instance.package_root local base_dir = auto_ssl_instance:get("dir") local hook_port = auto_ssl_instance:get("hook_server_port") + local multiname = auto_ssl_instance:get("multiname_cert") + domains = "--domain " .. domain .. " " assert(type(hook_port) == "number", "hook_port must be a number") assert(hook_port <= 65535, "hook_port must be below 65536") @@ -14,35 +16,29 @@ function _M.issue_cert(auto_ssl_instance, domain) "env HOOK_SECRET=" .. ngx.shared.auto_ssl:get("hook_server:secret") .. " " .. "HOOK_SERVER_PORT=" .. hook_port - -- The result of running that command should result in the certs being - -- populated in our storage (due to the deploy_cert hook triggering). - local storage = auto_ssl_instance:get("storage") - local fullchain_pem, privkey_pem = storage:get_cert(domain) + 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. -- -- Disable dehydrated's locking, since we perform our own domain-specific -- locking using the storage adapter. - local dom, z = storage:get_domains(domain) - local d, zz = storage:get_subdomains(dom) - function get_domain_list(domain) - local str = '' - for _, i in pairs(domain) do - str = str .. "--domain " .. i .. " " - end - return str - end - if d then - domainus = get_domain_list(d) - else - domainus = "--domain " .. z .. " " - end local command = env_vars .. " " .. package_root .. "/auto-ssl/vendor/dehydrated " .. "--cron " .. "--no-lock " .. - domainus .. + domains .. "--challenge http-01 " .. "--config " .. base_dir .. "/letsencrypt/config " .. "--hook " .. package_root .. "/auto-ssl/shell/letsencrypt_hooks" @@ -54,6 +50,10 @@ function _M.issue_cert(auto_ssl_instance, domain) ngx.log(ngx.DEBUG, "auto-ssl: dehydrated output: " .. out) + -- The result of running that command should result in the certs being + -- populated in our storage (due to the deploy_cert hook triggering). + local storage = auto_ssl_instance:get("storage") + local fullchain_pem, privkey_pem = storage:get_cert(domain) -- If dehydrated said it succeeded, but we still don't have any certs in -- storage, the issue is likely that the certs have been deleted out of our @@ -62,39 +62,21 @@ function _M.issue_cert(auto_ssl_instance, domain) -- storage with dehydrated's local copies. if not fullchain_pem or not privkey_pem then ngx.log(ngx.WARN, "auto-ssl: dehydrated succeeded, but certs still missing from storage - trying to manually copy - domain: " .. domain) - if d then - for _, i in pairs(d) do - command = env_vars .. " " .. - package_root .. "/auto-ssl/shell/letsencrypt_hooks " .. - "deploy_cert " .. - i .. " " .. - base_dir .. "/letsencrypt/certs/" .. i .. "/privkey.pem " .. - base_dir .. "/letsencrypt/certs/" .. i .. "/cert.pem " .. - base_dir .. "/letsencrypt/certs/" .. i .. "/fullchain.pem " .. - base_dir .. "/letsencrypt/certs/" .. i .. "/chain.pem " .. - math.floor(ngx.now()) - status, out, err = shell_execute(command) - if status ~= 0 then - ngx.log(ngx.ERR, "auto-ssl: dehydrated manual hook.sh failed: ", command, " status: ", status, " out: ", out, " err: ", err) - return nil, nil, "dehydrated failure" - end - end - else - command = env_vars .. " " .. - package_root .. "/auto-ssl/shell/letsencrypt_hooks " .. - "deploy_cert " .. - z .. " " .. - base_dir .. "/letsencrypt/certs/" .. z .. "/privkey.pem " .. - base_dir .. "/letsencrypt/certs/" .. z .. "/cert.pem " .. - base_dir .. "/letsencrypt/certs/" .. z .. "/fullchain.pem " .. - base_dir .. "/letsencrypt/certs/" .. z .. "/chain.pem " .. - math.floor(ngx.now()) - status, out, err = shell_execute(command) - if status ~= 0 then - ngx.log(ngx.ERR, "auto-ssl: dehydrated manual hook.sh failed: ", command, " status: ", status, " out: ", out, " err: ", err) - return nil, nil, "dehydrated failure" - end + command = env_vars .. " " .. + package_root .. "/auto-ssl/shell/letsencrypt_hooks " .. + "deploy_cert " .. + domain .. " " .. + base_dir .. "/letsencrypt/certs/" .. domain .. "/privkey.pem " .. + base_dir .. "/letsencrypt/certs/" .. domain .. "/cert.pem " .. + base_dir .. "/letsencrypt/certs/" .. domain .. "/fullchain.pem " .. + base_dir .. "/letsencrypt/certs/" .. domain .. "/chain.pem " .. + math.floor(ngx.now()) + status, out, err = shell_execute(command) + if status ~= 0 then + ngx.log(ngx.ERR, "auto-ssl: dehydrated manual hook.sh failed: ", command, " status: ", status, " out: ", out, " err: ", err) + return nil, nil, "dehydrated failure" end + -- Try fetching again. fullchain_pem, privkey_pem = storage:get_cert(domain) end @@ -107,4 +89,4 @@ function _M.issue_cert(auto_ssl_instance, domain) return fullchain_pem, privkey_pem 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 a0e1dc5..30068e3 100644 --- a/lib/resty/auto-ssl/storage.lua +++ b/lib/resty/auto-ssl/storage.lua @@ -1,22 +1,21 @@ local resty_random = require "resty.random" local str = require "resty.string" +local cjson = require "cjson" local _M = {} -local cjson = require "cjson" - function _M.new(adapter) return setmetatable({ adapter = adapter }, { __index = _M }) end -function _M.get_domains(self, domain) - function tablelength(a) - local count = 0 - for _ in pairs(a) do count = count + 1 end - return count - end +function tablelength(a) + local count = 0 + for _ in pairs(a) do count = count + 1 end + return count +end - function subdomain(a) +function _M.get_domains(self, domain, level) + local function subdomain(a) local x = {} for word in string.gmatch(a, '([^.]+)') do table.insert(x, word) @@ -24,31 +23,53 @@ function _M.get_domains(self, domain) 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 = ar[size-1] .. "." .. ar[size] - if size>2 then - return main_domain, domain + + local main_domain = get_name(ar, size, level) + if main_domain then + return main_domain, domain else - return main_domain, nil + return domain, nil end end -function _M.get_subdomains(self, domain) - function tablelength(a) - local count = 0 - for _ in pairs(a) do count = count + 1 end - return count - end - - function subdomains(a) +function _M.get_subdomain(self, domain) + local function subdomains(a) local x = {} - for word in string.gmatch(a, '([^:]+)') do + if a then + for word in string.gmatch(a, '([^:]+)') do table.insert(x, word) + end + return x end - return x + return nil end - local json, err = self.adapter:get(domain .. "main") + + 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 @@ -56,42 +77,109 @@ function _M.get_subdomains(self, domain) end local data = cjson.decode(json) local ar = subdomains(data['subdomain']) - local size = tablelength(ar) - return ar, size + local extended = subdomains(data['extended']) + local size = check_max_len(ar, tablelength(ar)) + return ar, size, nil, extended end -function _M.set_subdomains(self, domain, subdomain) - local x, n, err = self.get_subdomains(self, domain) - if err then - local data = cjson.encode({domain=domain, - subdomain=subdomain}) - self.adapter:set(domain .. "main", data) - else - for _, i in pairs(x) do +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 + return true end - end - local sub = table.concat(x, ":") - if nil == string.find(sub, subdomain) then - local sub = sub .. ":" .. subdomain - local data = cjson.encode({domain=domain, - subdomain=sub}) - self.adapter:set(domain .. "main", data) - 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 = self.get_subdomains(self, domain) - if x then - for _, i in pairs(x) do - if i == subdomain then - return domain + 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 - return nil + + 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) @@ -107,12 +195,7 @@ function _M.delete_challenge(self, domain, path) end function _M.get_cert(self, domain) - local d, z = self.get_domains(self, domain) - local c = self.check_subdomain(self, d, z) - if not c then - return nil - end - local json, err = self.adapter:get(d .. ":latest") + local json, err = self.adapter:get(domain .. ":latest") if err then return nil, nil, err elseif not json then @@ -130,7 +213,6 @@ function _M.set_cert(self, domain, fullchain_pem, privkey_pem, cert_pem) -- a single string (regardless of implementation), and we don't have to worry -- about race conditions with the public cert and private key being stored -- separately and getting out of sync. - local d, z = self.get_domains(self, domain) local data = cjson.encode({ fullchain_pem = fullchain_pem, privkey_pem = privkey_pem, @@ -140,10 +222,10 @@ function _M.set_cert(self, domain, fullchain_pem, privkey_pem, cert_pem) -- Store the cert with the current timestamp, so the old certs are preserved -- in case something goes wrong. local time = ngx.now() * 1000 - self.adapter:set(d .. ":" .. time, data) + self.adapter:set(domain .. ":" .. time, data) -- Store the cert under the "latest" alias, which is what this app will use. - return self.adapter:set(d .. ":latest", data) + return self.adapter:set(domain .. ":latest", data) end function _M.all_cert_domains(self) @@ -173,8 +255,7 @@ end -- but in combination with resty-lock, it should prevent the vast majority of -- double requests. function _M.issue_cert_lock(self, domain) - local d, z = self.get_domains(self, domain) - local key = d .. ":issue_cert_lock" + local key = domain .. ":issue_cert_lock" local lock_rand_value = str.to_hex(resty_random.bytes(32)) -- Wait up to 30 seconds for any existing locks to be unlocked. @@ -202,8 +283,7 @@ function _M.issue_cert_lock(self, domain) end function _M.issue_cert_unlock(self, domain, lock_rand_value) - local d, z = self.get_domains(self, domain) - local key = d .. ":issue_cert_lock" + local key = domain .. ":issue_cert_lock" -- Remove the existing lock if it matches the expected value. local current_value, err = self.adapter:get(key) @@ -216,4 +296,4 @@ function _M.issue_cert_unlock(self, domain, lock_rand_value) end end -return _M +return _M \ No newline at end of file