472 lines
13 KiB
Lua
472 lines
13 KiB
Lua
local util = require("resty.acme.util")
|
|
|
|
local helpers = require "spec.helpers"
|
|
local cjson = require "cjson"
|
|
|
|
local pkey = require("resty.openssl.pkey")
|
|
local x509 = require("resty.openssl.x509")
|
|
|
|
local cycle_aware_deep_copy = require("kong.tools.table").cycle_aware_deep_copy
|
|
|
|
local client
|
|
|
|
local function new_cert_key_pair(expire)
|
|
local key = pkey.new(nil, 'EC', 'prime256v1')
|
|
local crt = x509.new()
|
|
crt:set_pubkey(key)
|
|
crt:set_version(3)
|
|
if expire then
|
|
crt:set_not_after(expire)
|
|
end
|
|
crt:sign(key)
|
|
return key:to_PEM("private"), crt:to_PEM()
|
|
end
|
|
|
|
local strategies = {}
|
|
for _, strategy in helpers.each_strategy() do
|
|
table.insert(strategies, strategy)
|
|
end
|
|
table.insert(strategies, "off")
|
|
|
|
local proper_config = {
|
|
account_email = "someone@somedomain.com",
|
|
api_uri = "http://api.someacme.org",
|
|
storage = "shm",
|
|
storage_config = {
|
|
shm = { shm_name = "kong" },
|
|
},
|
|
renew_threshold_days = 30,
|
|
}
|
|
|
|
for _, strategy in ipairs(strategies) do
|
|
local _, db
|
|
|
|
lazy_setup(function()
|
|
_, db = helpers.get_db_utils(strategy, {
|
|
"acme_storage"
|
|
}, { "acme", })
|
|
|
|
client = require("kong.plugins.acme.client")
|
|
|
|
local account_name = client._account_name(proper_config)
|
|
|
|
local fake_cache = {
|
|
[account_name] = {
|
|
key = util.create_pkey(),
|
|
kid = "fake kid url",
|
|
},
|
|
}
|
|
|
|
kong.cache = {
|
|
get = function(_, _, _, f, _, k)
|
|
return fake_cache[k]
|
|
end
|
|
}
|
|
|
|
db.acme_storage:insert {
|
|
key = account_name,
|
|
value = fake_cache[account_name],
|
|
}
|
|
|
|
end)
|
|
|
|
describe("Plugin: acme (client.new) [#" .. strategy .. "]", function()
|
|
it("rejects invalid account config", function()
|
|
local c, err = client.new({
|
|
storage = "shm",
|
|
storage_config = {
|
|
shm = nil,
|
|
},
|
|
api_uri = proper_config.api_uri,
|
|
account_email = "notme@exmaple.com",
|
|
})
|
|
assert.is_nil(c)
|
|
assert.equal(err, "shm is not defined in plugin storage config")
|
|
end)
|
|
|
|
it("creates acme client properly", function()
|
|
local c, err = client.new(proper_config)
|
|
assert.is_nil(err)
|
|
assert.not_nil(c)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
for _, strategy in ipairs(strategies) do
|
|
local account_name, account_key
|
|
local c, config, db
|
|
|
|
local KEY_ID = "123"
|
|
local KEY_SET_NAME = "key_set_foo"
|
|
|
|
local pem_pub, pem_priv
|
|
|
|
lazy_setup(function()
|
|
pem_pub, pem_priv = helpers.generate_keys("PEM")
|
|
client = require("kong.plugins.acme.client")
|
|
account_name = client._account_name(proper_config)
|
|
end)
|
|
|
|
describe("Plugin: acme (client.create_account) [#" .. strategy .. "]", function()
|
|
describe("create with preconfigured account_key with key_set", function()
|
|
lazy_setup(function()
|
|
account_key = {key_id = KEY_ID, key_set = KEY_SET_NAME}
|
|
config = cycle_aware_deep_copy(proper_config)
|
|
config.account_key = account_key
|
|
c = client.new(config)
|
|
|
|
_, db = helpers.get_db_utils(strategy ~= "off" and strategy or nil, {"keys", "key_sets"})
|
|
|
|
local ks, err = assert(db.key_sets:insert({name = KEY_SET_NAME}))
|
|
assert.is_nil(err)
|
|
|
|
local k, err = db.keys:insert({
|
|
name = "Test PEM",
|
|
pem = {
|
|
private_key = pem_priv,
|
|
public_key = pem_pub
|
|
},
|
|
set = ks,
|
|
kid = KEY_ID
|
|
})
|
|
assert(k)
|
|
assert.is_nil(err)
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
c.storage:delete(account_name)
|
|
end)
|
|
|
|
-- The first call should result in the account key being persisted.
|
|
it("persists account", function()
|
|
local err = client._create_account(config)
|
|
assert.is_nil(err)
|
|
|
|
local account, err = c.storage:get(account_name)
|
|
assert.is_nil(err)
|
|
assert.not_nil(account)
|
|
|
|
local account_data = cjson.decode(account)
|
|
assert.equal(account_data.key, pem_priv)
|
|
end)
|
|
|
|
-- The second call should be a nop because the key is found in the db.
|
|
-- Validate that the second call does not result in the key being changed.
|
|
it("skips persisting existing account", function()
|
|
local err = client._create_account(config)
|
|
assert.is_nil(err)
|
|
|
|
local account, err = c.storage:get(account_name)
|
|
assert.is_nil(err)
|
|
assert.not_nil(account)
|
|
|
|
local account_data = cjson.decode(account)
|
|
assert.equal(account_data.key, pem_priv)
|
|
end)
|
|
end)
|
|
|
|
describe("create with preconfigured account_key without key_set", function()
|
|
lazy_setup(function()
|
|
account_key = {key_id = KEY_ID}
|
|
config = cycle_aware_deep_copy(proper_config)
|
|
config.account_key = account_key
|
|
c = client.new(config)
|
|
|
|
_, db = helpers.get_db_utils(strategy ~= "off" and strategy or nil, {"keys", "key_sets"})
|
|
|
|
local k, err = db.keys:insert({
|
|
name = "Test PEM",
|
|
pem = {
|
|
private_key = pem_priv,
|
|
public_key = pem_pub
|
|
},
|
|
kid = KEY_ID
|
|
})
|
|
assert(k)
|
|
assert.is_nil(err)
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
c.storage:delete(account_name)
|
|
end)
|
|
|
|
-- The first call should result in the account key being persisted.
|
|
it("persists account", function()
|
|
local err = client._create_account(config)
|
|
assert.is_nil(err)
|
|
|
|
local account, err = c.storage:get(account_name)
|
|
assert.is_nil(err)
|
|
assert.not_nil(account)
|
|
|
|
local account_data = cjson.decode(account)
|
|
assert.equal(account_data.key, pem_priv)
|
|
end)
|
|
end)
|
|
|
|
describe("create with generated account_key", function()
|
|
local i = 1
|
|
local account_keys = {}
|
|
|
|
lazy_setup(function()
|
|
config = cycle_aware_deep_copy(proper_config)
|
|
c = client.new(config)
|
|
|
|
account_keys[1] = util.create_pkey()
|
|
account_keys[2] = util.create_pkey()
|
|
|
|
util.create_pkey = function(size, type)
|
|
local key = account_keys[i]
|
|
i = i + 1
|
|
return key
|
|
end
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
c.storage:delete(account_name)
|
|
end)
|
|
|
|
-- The first call should result in a key being generated and the account
|
|
-- should then be persisted.
|
|
it("persists account", function()
|
|
local err = client._create_account(config)
|
|
assert.is_nil(err)
|
|
|
|
local account, err = c.storage:get(account_name)
|
|
assert.is_nil(err)
|
|
assert.not_nil(account)
|
|
|
|
local account_data = cjson.decode(account)
|
|
assert.equal(account_data.key, account_keys[1])
|
|
end)
|
|
|
|
-- The second call should be a nop because the key is found in the db.
|
|
it("skip persisting existing account", function()
|
|
local err = client._create_account(config)
|
|
assert.is_nil(err)
|
|
|
|
local account, err = c.storage:get(account_name)
|
|
assert.is_nil(err)
|
|
assert.not_nil(account)
|
|
|
|
local account_data = cjson.decode(account)
|
|
assert.equal(account_data.key, account_keys[1])
|
|
end)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
for _, strategy in helpers.each_strategy() do
|
|
describe("Plugin: acme (client.save) [#" .. strategy .. "]", function()
|
|
local bp, db
|
|
local cert, sni
|
|
local host = "test1.test"
|
|
|
|
lazy_setup(function()
|
|
bp, db = helpers.get_db_utils(strategy, {
|
|
"certificates",
|
|
"snis",
|
|
}, { "acme", })
|
|
|
|
local key, crt = new_cert_key_pair()
|
|
cert = bp.certificates:insert {
|
|
cert = crt,
|
|
key = key,
|
|
tags = { "managed-by-acme" },
|
|
}
|
|
|
|
sni = bp.snis:insert {
|
|
name = host,
|
|
certificate = cert,
|
|
tags = { "managed-by-acme" },
|
|
}
|
|
|
|
end)
|
|
|
|
describe("creates new cert", function()
|
|
local key, crt = new_cert_key_pair()
|
|
local new_sni, new_cert, err
|
|
local new_host = "test2.test"
|
|
|
|
it("returns no error", function()
|
|
err = client._save_dao(new_host, key, crt)
|
|
assert.is_nil(err)
|
|
end)
|
|
|
|
it("create new sni", function()
|
|
new_sni, err = db.snis:select_by_name(new_host)
|
|
assert.is_nil(err)
|
|
assert.not_nil(new_sni.certificate.id)
|
|
end)
|
|
|
|
it("create new certificate", function()
|
|
new_cert, err = db.certificates:select(new_sni.certificate)
|
|
assert.is_nil(err)
|
|
assert.same(new_cert.key, key)
|
|
assert.same(new_cert.cert, crt)
|
|
end)
|
|
end)
|
|
|
|
describe("update", function()
|
|
local key, crt = new_cert_key_pair()
|
|
local new_sni, new_cert, err
|
|
|
|
it("returns no error", function()
|
|
err = client._save_dao(host, key, crt)
|
|
assert.is_nil(err)
|
|
end)
|
|
|
|
it("updates existing sni", function()
|
|
new_sni, err = db.snis:select_by_name(host)
|
|
assert.is_nil(err)
|
|
assert.same(new_sni.id, sni.id)
|
|
assert.not_nil(new_sni.certificate.id)
|
|
assert.not_same(new_sni.certificate.id, sni.certificate.id)
|
|
end)
|
|
|
|
it("creates new certificate", function()
|
|
new_cert, err = db.certificates:select(new_sni.certificate)
|
|
assert.is_nil(err)
|
|
assert.same(new_cert.key, key)
|
|
assert.same(new_cert.cert, crt)
|
|
end)
|
|
|
|
it("deletes old certificate", function()
|
|
new_cert, err = db.certificates:select(cert)
|
|
assert.is_nil(err)
|
|
assert.is_nil(new_cert)
|
|
end)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
for _, strategy in ipairs({"off"}) do
|
|
describe("Plugin: acme (client.renew) [#" .. strategy .. "]", function()
|
|
local bp
|
|
local cert
|
|
local host = "test1.test"
|
|
local host_not_expired = "test2.test"
|
|
-- make it due for renewal
|
|
local key, crt = new_cert_key_pair(ngx.time() - 23333)
|
|
-- make it not due for renewal
|
|
local key_not_expired, crt_not_expired = new_cert_key_pair(ngx.time() + 365 * 86400)
|
|
|
|
lazy_setup(function()
|
|
bp, _ = helpers.get_db_utils(strategy, {
|
|
"certificates",
|
|
"snis",
|
|
}, { "acme", })
|
|
|
|
cert = bp.certificates:insert {
|
|
cert = crt,
|
|
key = key,
|
|
tags = { "managed-by-acme" },
|
|
}
|
|
|
|
bp.snis:insert {
|
|
name = host,
|
|
certificate = cert,
|
|
tags = { "managed-by-acme" },
|
|
}
|
|
|
|
cert = bp.certificates:insert {
|
|
cert = crt_not_expired,
|
|
key = key_not_expired,
|
|
tags = { "managed-by-acme" },
|
|
}
|
|
|
|
bp.snis:insert {
|
|
name = host_not_expired,
|
|
certificate = cert,
|
|
tags = { "managed-by-acme" },
|
|
}
|
|
|
|
client = require("kong.plugins.acme.client")
|
|
-- hack in unit test mode
|
|
client._set_is_dbless(strategy == "off")
|
|
end)
|
|
|
|
describe("", function()
|
|
it("deletes renew config is cert is deleted", function()
|
|
local c, err = client.new(proper_config)
|
|
assert.is_nil(err)
|
|
|
|
local host = "dne.konghq.com"
|
|
-- write a dummy renew config
|
|
err = c.storage:set(client._renew_key_prefix .. host, cjson.encode({
|
|
host = host,
|
|
-- make it due for renewal
|
|
expire_at = ngx.time() - 23333,
|
|
}))
|
|
assert.is_nil(err)
|
|
-- do the renewal
|
|
err = client._renew_certificate_storage(proper_config)
|
|
assert.is_nil(err)
|
|
-- the dummy config should now be deleted
|
|
local v, err = c.storage:get(client._renew_key_prefix .. host)
|
|
assert.is_nil(err)
|
|
assert.is_nil(v)
|
|
end)
|
|
|
|
it("renews a certificate when it's expired", function()
|
|
local c, err = client.new(proper_config)
|
|
|
|
assert.is_nil(err)
|
|
if strategy == "off" then
|
|
err = c.storage:set(client._certkey_key_prefix .. host, cjson.encode({
|
|
cert = crt,
|
|
key = key,
|
|
}))
|
|
assert.is_nil(err)
|
|
end
|
|
|
|
local certkey, err = client.load_certkey(proper_config, host)
|
|
assert.is_nil(err)
|
|
assert.not_nil(certkey)
|
|
assert.not_nil(certkey.cert)
|
|
assert.not_nil(certkey.key)
|
|
-- check renewal
|
|
local renew, err = client._check_expire(certkey.cert, 30 * 86400)
|
|
assert.is_nil(err)
|
|
assert.is_truthy(renew)
|
|
end)
|
|
|
|
it("does not renew a certificate when it's not expired", function()
|
|
local c, err = client.new(proper_config)
|
|
|
|
assert.is_nil(err)
|
|
if strategy == "off" then
|
|
err = c.storage:set(client._certkey_key_prefix .. host_not_expired, cjson.encode({
|
|
cert = crt_not_expired,
|
|
key = key_not_expired,
|
|
}))
|
|
assert.is_nil(err)
|
|
end
|
|
|
|
local certkey, err = client.load_certkey(proper_config, host_not_expired)
|
|
assert.is_nil(err)
|
|
assert.not_nil(certkey)
|
|
assert.not_nil(certkey.cert)
|
|
assert.not_nil(certkey.key)
|
|
-- check renewal
|
|
local renew, err = client._check_expire(certkey.cert, 30 * 86400)
|
|
assert.is_nil(err)
|
|
assert.is_falsy(renew)
|
|
end)
|
|
|
|
it("calling handler.renew with a false argument should be successful", function()
|
|
local handler = require("kong.plugins.acme.handler")
|
|
handler:configure({{domains = {"example.com"}}})
|
|
|
|
local original = client.renew_certificate
|
|
client.renew_certificate = function (config)
|
|
print("mock renew_certificate")
|
|
end
|
|
handler.renew(false)
|
|
client.renew_certificate = original
|
|
end)
|
|
end)
|
|
|
|
end)
|
|
end
|