Add lua endpoint to handle certificates in dynamic configuration mode
This commit is contained in:
parent
82bfcb0337
commit
5200a38bd7
4 changed files with 194 additions and 0 deletions
|
@ -23,6 +23,7 @@ resty \
|
||||||
-I /usr/local/lib/lua \
|
-I /usr/local/lib/lua \
|
||||||
-I /usr/lib/lua-platform-path/lua/5.1 \
|
-I /usr/lib/lua-platform-path/lua/5.1 \
|
||||||
--shdict "configuration_data 5M" \
|
--shdict "configuration_data 5M" \
|
||||||
|
--shdict "certificate_data 16M" \
|
||||||
--shdict "balancer_ewma 1M" \
|
--shdict "balancer_ewma 1M" \
|
||||||
--shdict "balancer_ewma_last_touched_at 1M" \
|
--shdict "balancer_ewma_last_touched_at 1M" \
|
||||||
./rootfs/etc/nginx/lua/test/run.lua ${BUSTED_ARGS} ./rootfs/etc/nginx/lua/test/
|
./rootfs/etc/nginx/lua/test/run.lua ${BUSTED_ARGS} ./rootfs/etc/nginx/lua/test/
|
||||||
|
|
|
@ -192,6 +192,7 @@ func buildLuaSharedDictionaries(s interface{}, dynamicConfigurationEnabled bool,
|
||||||
if dynamicConfigurationEnabled {
|
if dynamicConfigurationEnabled {
|
||||||
out = append(out,
|
out = append(out,
|
||||||
"lua_shared_dict configuration_data 5M",
|
"lua_shared_dict configuration_data 5M",
|
||||||
|
"lua_shared_dict certificate_data 16M",
|
||||||
"lua_shared_dict locks 512k",
|
"lua_shared_dict locks 512k",
|
||||||
"lua_shared_dict balancer_ewma 1M",
|
"lua_shared_dict balancer_ewma 1M",
|
||||||
"lua_shared_dict balancer_ewma_last_touched_at 1M",
|
"lua_shared_dict balancer_ewma_last_touched_at 1M",
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
local json = require("cjson")
|
||||||
|
|
||||||
-- this is the Lua representation of Configuration struct in internal/ingress/types.go
|
-- this is the Lua representation of Configuration struct in internal/ingress/types.go
|
||||||
local configuration_data = ngx.shared.configuration_data
|
local configuration_data = ngx.shared.configuration_data
|
||||||
|
local certificate_data = ngx.shared.certificate_data
|
||||||
|
|
||||||
local _M = {
|
local _M = {
|
||||||
nameservers = {}
|
nameservers = {}
|
||||||
|
@ -29,6 +32,55 @@ local function fetch_request_body()
|
||||||
return body
|
return body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function _M.get_pem_cert_key(hostname)
|
||||||
|
return certificate_data:get(hostname)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_servers()
|
||||||
|
if ngx.var.request_method ~= "POST" then
|
||||||
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
||||||
|
ngx.print("Only POST requests are allowed!")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local raw_servers = fetch_request_body()
|
||||||
|
|
||||||
|
local ok, servers = pcall(json.decode, raw_servers)
|
||||||
|
if not ok then
|
||||||
|
ngx.log(ngx.ERR, "could not parse servers: " .. tostring(servers))
|
||||||
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local err_buf = {}
|
||||||
|
for _, server in ipairs(servers) do
|
||||||
|
if server.hostname and server.sslCert.pemCertKey then
|
||||||
|
local success, err = certificate_data:safe_set(server.hostname, server.sslCert.pemCertKey)
|
||||||
|
if not success then
|
||||||
|
if err == "no memory" then
|
||||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||||
|
ngx.log(ngx.ERR, "no memory in certificate_data dictionary")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local err_msg = string.format("error setting certificate for %s: %s\n",
|
||||||
|
server.hostname, tostring(err))
|
||||||
|
table.insert(err_buf, err_msg)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ngx.log(ngx.WARN, "hostname or pemCertKey are not present")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #err_buf > 0 then
|
||||||
|
ngx.log(ngx.ERR, table.concat(err_buf))
|
||||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
ngx.status = ngx.HTTP_CREATED
|
||||||
|
end
|
||||||
|
|
||||||
function _M.call()
|
function _M.call()
|
||||||
if ngx.var.request_method ~= "POST" and ngx.var.request_method ~= "GET" then
|
if ngx.var.request_method ~= "POST" and ngx.var.request_method ~= "GET" then
|
||||||
ngx.status = ngx.HTTP_BAD_REQUEST
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
||||||
|
@ -36,6 +88,11 @@ function _M.call()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if ngx.var.request_uri == "/configuration/servers" then
|
||||||
|
handle_servers()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if ngx.var.request_uri ~= "/configuration/backends" then
|
if ngx.var.request_uri ~= "/configuration/backends" then
|
||||||
ngx.status = ngx.HTTP_NOT_FOUND
|
ngx.status = ngx.HTTP_NOT_FOUND
|
||||||
ngx.print("Not found!")
|
ngx.print("Not found!")
|
||||||
|
@ -65,4 +122,8 @@ function _M.call()
|
||||||
ngx.status = ngx.HTTP_CREATED
|
ngx.status = ngx.HTTP_CREATED
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if _TEST then
|
||||||
|
_M.handle_servers = handle_servers
|
||||||
|
end
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
|
131
rootfs/etc/nginx/lua/test/configuration_test.lua
Normal file
131
rootfs/etc/nginx/lua/test/configuration_test.lua
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
_G._TEST = true
|
||||||
|
local cjson = require("cjson")
|
||||||
|
local configuration = require("configuration")
|
||||||
|
|
||||||
|
local unmocked_ngx = _G.ngx
|
||||||
|
local certificate_data = ngx.shared.certificate_data
|
||||||
|
|
||||||
|
function get_mocked_ngx_env()
|
||||||
|
local _ngx = {}
|
||||||
|
setmetatable(_ngx, {__index = _G.ngx})
|
||||||
|
|
||||||
|
_ngx.status = 100
|
||||||
|
_ngx.var = {}
|
||||||
|
_ngx.req = {
|
||||||
|
read_body = function() end,
|
||||||
|
get_body_file = function() end,
|
||||||
|
}
|
||||||
|
return _ngx
|
||||||
|
end
|
||||||
|
|
||||||
|
describe("Configuration", function()
|
||||||
|
before_each(function()
|
||||||
|
_G.ngx = get_mocked_ngx_env()
|
||||||
|
end)
|
||||||
|
|
||||||
|
after_each(function()
|
||||||
|
_G.ngx = unmocked_ngx
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("handle_servers()", function()
|
||||||
|
it("should not accept non POST methods", function()
|
||||||
|
ngx.var.request_method = "GET"
|
||||||
|
|
||||||
|
local s = spy.on(ngx, "print")
|
||||||
|
assert.has_no.errors(configuration.handle_servers)
|
||||||
|
assert.spy(s).was_called_with("Only POST requests are allowed!")
|
||||||
|
assert.same(ngx.status, ngx.HTTP_BAD_REQUEST)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should ignore servers that don't have hostname or pemCertKey set", function()
|
||||||
|
ngx.var.request_method = "POST"
|
||||||
|
local mock_servers = cjson.encode({
|
||||||
|
{
|
||||||
|
hostname = "hostname",
|
||||||
|
sslCert = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sslCert = {
|
||||||
|
pemCertKey = "pemCertKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ngx.req.get_body_data = function() return mock_servers end
|
||||||
|
|
||||||
|
local s = spy.on(ngx, "log")
|
||||||
|
assert.has_no.errors(configuration.handle_servers)
|
||||||
|
assert.spy(s).was_called_with(ngx.WARN, "hostname or pemCertKey are not present")
|
||||||
|
assert.same(ngx.status, ngx.HTTP_CREATED)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should successfully update certificates and keys for each host", function()
|
||||||
|
ngx.var.request_method = "POST"
|
||||||
|
local mock_servers = cjson.encode({
|
||||||
|
{
|
||||||
|
hostname = "hostname",
|
||||||
|
sslCert = {
|
||||||
|
pemCertKey = "pemCertKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ngx.req.get_body_data = function() return mock_servers end
|
||||||
|
|
||||||
|
assert.has_no.errors(configuration.handle_servers)
|
||||||
|
assert.same(certificate_data:get("hostname"), "pemCertKey")
|
||||||
|
assert.same(ngx.status, ngx.HTTP_CREATED)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should log an err and set status to Internal Server Error when a certificate cannot be set", function()
|
||||||
|
ngx.var.request_method = "POST"
|
||||||
|
ngx.shared.certificate_data.safe_set = function(self, data) return false, "error" end
|
||||||
|
local mock_servers = cjson.encode({
|
||||||
|
{
|
||||||
|
hostname = "hostname",
|
||||||
|
sslCert = {
|
||||||
|
pemCertKey = "pemCertKey"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hostname = "hostname2",
|
||||||
|
sslCert = {
|
||||||
|
pemCertKey = "pemCertKey2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ngx.req.get_body_data = function() return mock_servers end
|
||||||
|
|
||||||
|
local s = spy.on(ngx, "log")
|
||||||
|
assert.has_no.errors(configuration.handle_servers)
|
||||||
|
assert.spy(s).was_called_with(ngx.ERR,
|
||||||
|
"error setting certificate for hostname: error\nerror setting certificate for hostname2: error\n")
|
||||||
|
assert.same(ngx.status, ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should log an err, set status to Internal Server Error, and short circuit when shared dictionary is full", function()
|
||||||
|
ngx.var.request_method = "POST"
|
||||||
|
ngx.shared.certificate_data.safe_set = function(self, data) return false, "no memory" end
|
||||||
|
local mock_servers = cjson.encode({
|
||||||
|
{
|
||||||
|
hostname = "hostname",
|
||||||
|
sslCert = {
|
||||||
|
pemCertKey = "pemCertKey"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hostname = "hostname2",
|
||||||
|
sslCert = {
|
||||||
|
pemCertKey = "pemCertKey2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ngx.req.get_body_data = function() return mock_servers end
|
||||||
|
|
||||||
|
local s1 = spy.on(ngx, "log")
|
||||||
|
local s2 = spy.on(ngx.shared.certificate_data, "safe_set")
|
||||||
|
assert.has_no.errors(configuration.handle_servers)
|
||||||
|
assert.spy(s1).was_called_with(ngx.ERR, "no memory in certificate_data dictionary")
|
||||||
|
assert.spy(s2).was_not_called_with("hostname2", "pemCertKey2")
|
||||||
|
assert.same(ngx.status, ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
Loading…
Reference in a new issue