From 57db904c929ca73744707ea124288d73f7bde0da Mon Sep 17 00:00:00 2001 From: Elvin Efendi Date: Mon, 26 Aug 2019 13:05:05 -0400 Subject: [PATCH] fix lua certificate handling tests --- .../etc/nginx/lua/test/certificate_test.lua | 35 +- .../etc/nginx/lua/test/configuration_test.lua | 450 ++++++++---------- 2 files changed, 226 insertions(+), 259 deletions(-) diff --git a/rootfs/etc/nginx/lua/test/certificate_test.lua b/rootfs/etc/nginx/lua/test/certificate_test.lua index d6581177b..5cbfbe4fc 100644 --- a/rootfs/etc/nginx/lua/test/certificate_test.lua +++ b/rootfs/etc/nginx/lua/test/certificate_test.lua @@ -34,6 +34,17 @@ local function refute_certificate_is_set() assert.spy(ssl.set_der_priv_key).was_not_called() end +local function set_certificate(hostname, certificate, uuid) + local success, err = ngx.shared.certificate_servers:set(hostname, uuid) + if not success then + error(err) + end + success, err = ngx.shared.certificate_data:set(uuid, certificate) + if not success then + error(err) + end +end + local unmocked_ngx = _G.ngx describe("Certificate", function() @@ -47,8 +58,7 @@ describe("Certificate", function() ngx.exit = function(status) end - ngx.shared.certificate_servers:set(DEFAULT_CERT_HOSTNAME, DEFAULT_UUID) - ngx.shared.certificate_data:set(DEFAULT_UUID, DEFAULT_CERT) + set_certificate(DEFAULT_CERT_HOSTNAME, DEFAULT_CERT, DEFAULT_UUID) end) after_each(function() @@ -58,46 +68,40 @@ describe("Certificate", function() end) it("sets certificate and key when hostname is found in dictionary", function() - ngx.shared.certificate_servers:set("hostname", UUID) - ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT) - + set_certificate("hostname", EXAMPLE_CERT, UUID) assert_certificate_is_set(EXAMPLE_CERT) end) it("sets certificate and key for wildcard cert", function() ssl.server_name = function() return "sub.hostname", nil end - ngx.shared.certificate_servers:set("*.hostname", UUID) - ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT) + set_certificate("*.hostname", EXAMPLE_CERT, UUID) assert_certificate_is_set(EXAMPLE_CERT) end) it("sets certificate and key for domain with trailing dot", function() ssl.server_name = function() return "hostname.", nil end - ngx.shared.certificate_servers:set("hostname", UUID) - ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT) + set_certificate("hostname", EXAMPLE_CERT, UUID) assert_certificate_is_set(EXAMPLE_CERT) end) it("fallbacks to default certificate and key for domain with many trailing dots", function() ssl.server_name = function() return "hostname..", nil end - ngx.shared.certificate_servers:set("hostname", UUID) - ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT) + set_certificate("hostname", EXAMPLE_CERT, UUID) assert_certificate_is_set(DEFAULT_CERT) end) it("sets certificate and key for nested wildcard cert", function() ssl.server_name = function() return "sub.nested.hostname", nil end - ngx.shared.certificate_servers:set("*.nested.hostname", UUID) - ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT) + set_certificate("*.nested.hostname", EXAMPLE_CERT, UUID) assert_certificate_is_set(EXAMPLE_CERT) end) it("logs error message when certificate in dictionary is invalid", function() - ngx.shared.certificate_servers:set("hostname", "something invalid") + set_certificate("hostname", "something invalid", UUID) spy.on(ngx, "log") @@ -117,8 +121,7 @@ describe("Certificate", function() end) it("fails when hostname does not have certificate and default cert is invalid", function() - ngx.shared.certificate_servers:set(DEFAULT_CERT_HOSTNAME, UID) - ngx.shared.certificate_data:set(UID, "invalid") + set_certificate(DEFAULT_CERT_HOSTNAME, "invalid", UUID) spy.on(ngx, "log") diff --git a/rootfs/etc/nginx/lua/test/configuration_test.lua b/rootfs/etc/nginx/lua/test/configuration_test.lua index 74834ed7b..add5c9098 100644 --- a/rootfs/etc/nginx/lua/test/configuration_test.lua +++ b/rootfs/etc/nginx/lua/test/configuration_test.lua @@ -4,269 +4,233 @@ local configuration = require("configuration") local unmocked_ngx = _G.ngx local certificate_data = ngx.shared.certificate_data +local certificate_servers = ngx.shared.certificate_servers function get_backends() - return { - { - name = "my-dummy-backend-1", ["load-balance"] = "sticky", - endpoints = { { address = "10.183.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }, - sessionAffinityConfig = { name = "cookie", cookieSessionAffinity = { name = "route" } }, - }, - { - name = "my-dummy-backend-2", ["load-balance"] = "ewma", - endpoints = { - { address = "10.184.7.40", port = "7070", maxFails = 3, failTimeout = 2 }, - { address = "10.184.7.41", port = "7070", maxFails = 2, failTimeout = 1 }, - } - }, - { - name = "my-dummy-backend-3", ["load-balance"] = "round_robin", - endpoints = { - { address = "10.185.7.40", port = "6060", maxFails = 0, failTimeout = 0 }, - { address = "10.185.7.41", port = "6060", maxFails = 2, failTimeout = 1 }, - } - }, - } + return { + { + name = "my-dummy-backend-1", ["load-balance"] = "sticky", + endpoints = { { address = "10.183.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }, + sessionAffinityConfig = { name = "cookie", cookieSessionAffinity = { name = "route" } }, + }, + { + name = "my-dummy-backend-2", ["load-balance"] = "ewma", + endpoints = { + { address = "10.184.7.40", port = "7070", maxFails = 3, failTimeout = 2 }, + { address = "10.184.7.41", port = "7070", maxFails = 2, failTimeout = 1 }, + } + }, + { + name = "my-dummy-backend-3", ["load-balance"] = "round_robin", + endpoints = { + { address = "10.185.7.40", port = "6060", maxFails = 0, failTimeout = 0 }, + { address = "10.185.7.41", port = "6060", maxFails = 2, failTimeout = 1 }, + } + }, + } end function get_mocked_ngx_env() - local _ngx = { - status = ngx.HTTP_OK, - var = {}, - req = { - read_body = function() end, - get_body_data = function() return cjson.encode(get_backends()) end, - get_body_file = function() return nil end, - }, - log = function(msg) end, - } - setmetatable(_ngx, {__index = _G.ngx}) - return _ngx + local _ngx = { + status = ngx.HTTP_OK, + var = {}, + req = { + read_body = function() end, + get_body_data = function() return cjson.encode(get_backends()) end, + get_body_file = function() return nil end, + }, + log = function(msg) end, + } + setmetatable(_ngx, {__index = _G.ngx}) + return _ngx end describe("Configuration", function() - before_each(function() - _G.ngx = get_mocked_ngx_env() + before_each(function() + _G.ngx = get_mocked_ngx_env() + end) + + after_each(function() + _G.ngx = unmocked_ngx + package.loaded["configuration"] = nil + configuration = require("configuration") + end) + + describe("Backends", function() + context("Request method is neither GET nor POST", function() + it("sends 'Only POST and GET requests are allowed!' in the response body", function() + ngx.var.request_method = "PUT" + local s = spy.on(ngx, "print") + assert.has_no.errors(configuration.call) + assert.spy(s).was_called_with("Only POST and GET requests are allowed!") + end) + + it("returns a status code of 400", function() + ngx.var.request_method = "PUT" + assert.has_no.errors(configuration.call) + assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST) + end) end) - after_each(function() - _G.ngx = unmocked_ngx - package.loaded["configuration"] = nil - configuration = require("configuration") + context("GET request to /configuration/backends", function() + before_each(function() + ngx.var.request_method = "GET" + ngx.var.request_uri = "/configuration/backends" + end) + + it("returns the current configured backends on the response body", function() + -- Encoding backends since comparing tables fail due to reference comparison + local encoded_backends = cjson.encode(get_backends()) + ngx.shared.configuration_data:set("backends", encoded_backends) + local s = spy.on(ngx, "print") + assert.has_no.errors(configuration.call) + assert.spy(s).was_called_with(encoded_backends) + end) + + it("returns a status of 200", function() + assert.has_no.errors(configuration.call) + assert.equal(ngx.status, ngx.HTTP_OK) + end) end) - describe("Backends", function() - context("Request method is neither GET nor POST", function() - it("sends 'Only POST and GET requests are allowed!' in the response body", function() - ngx.var.request_method = "PUT" - local s = spy.on(ngx, "print") - assert.has_no.errors(configuration.call) - assert.spy(s).was_called_with("Only POST and GET requests are allowed!") - end) + context("POST request to /configuration/backends", function() + before_each(function() + ngx.var.request_method = "POST" + ngx.var.request_uri = "/configuration/backends" + end) - it("returns a status code of 400", function() - ngx.var.request_method = "PUT" - assert.has_no.errors(configuration.call) - assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST) - end) + it("stores the posted backends on the shared dictionary", function() + -- Encoding backends since comparing tables fail due to reference comparison + assert.has_no.errors(configuration.call) + assert.equal(ngx.shared.configuration_data:get("backends"), cjson.encode(get_backends())) + end) + + context("Failed to read request body", function() + local mocked_get_body_data = ngx.req.get_body_data + before_each(function() + ngx.req.get_body_data = function() return nil end end) - context("GET request to /configuration/backends", function() - before_each(function() - ngx.var.request_method = "GET" - ngx.var.request_uri = "/configuration/backends" - end) - - it("returns the current configured backends on the response body", function() - -- Encoding backends since comparing tables fail due to reference comparison - local encoded_backends = cjson.encode(get_backends()) - ngx.shared.configuration_data:set("backends", encoded_backends) - local s = spy.on(ngx, "print") - assert.has_no.errors(configuration.call) - assert.spy(s).was_called_with(encoded_backends) - end) - - it("returns a status of 200", function() - assert.has_no.errors(configuration.call) - assert.equal(ngx.status, ngx.HTTP_OK) - end) + teardown(function() + ngx.req.get_body_data = mocked_get_body_data end) - context("POST request to /configuration/backends", function() - before_each(function() - ngx.var.request_method = "POST" - ngx.var.request_uri = "/configuration/backends" - end) - - it("stores the posted backends on the shared dictionary", function() - -- Encoding backends since comparing tables fail due to reference comparison - assert.has_no.errors(configuration.call) - assert.equal(ngx.shared.configuration_data:get("backends"), cjson.encode(get_backends())) - end) - - context("Failed to read request body", function() - local mocked_get_body_data = ngx.req.get_body_data - before_each(function() - ngx.req.get_body_data = function() return nil end - end) - - teardown(function() - ngx.req.get_body_data = mocked_get_body_data - end) - - it("returns a status of 400", function() - local original_io_open = _G.io.open - _G.io.open = function(filename, extension) return false end - assert.has_no.errors(configuration.call) - assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST) - _G.io.open = original_io_open - end) - - it("logs 'dynamic-configuration: unable to read valid request body to stderr'", function() - local original_io_open = _G.io.open - _G.io.open = function(filename, extension) return false end - local s = spy.on(ngx, "log") - assert.has_no.errors(configuration.call) - assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: unable to read valid request body") - _G.io.open = original_io_open - end) - end) - - context("Failed to set the new backends to the configuration dictionary", function() - local resty_configuration_data_set = ngx.shared.configuration_data.set - before_each(function() - ngx.shared.configuration_data.set = function(key, value) return false, "" end - end) - - teardown(function() - ngx.shared.configuration_data.set = resty_configuration_data_set - end) - - it("returns a status of 400", function() - assert.has_no.errors(configuration.call) - assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST) - end) - - it("logs 'dynamic-configuration: error updating configuration:' to stderr", function() - local s = spy.on(ngx, "log") - assert.has_no.errors(configuration.call) - assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: error updating configuration: ") - end) - end) - - context("Succeeded to update backends configuration", function() - it("returns a status of 201", function() - assert.has_no.errors(configuration.call) - assert.equal(ngx.status, ngx.HTTP_CREATED) - end) - end) + it("returns a status of 400", function() + local original_io_open = _G.io.open + _G.io.open = function(filename, extension) return false end + assert.has_no.errors(configuration.call) + assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST) + _G.io.open = original_io_open end) + + it("logs 'dynamic-configuration: unable to read valid request body to stderr'", function() + local original_io_open = _G.io.open + _G.io.open = function(filename, extension) return false end + local s = spy.on(ngx, "log") + assert.has_no.errors(configuration.call) + assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: unable to read valid request body") + _G.io.open = original_io_open + end) + end) + + context("Failed to set the new backends to the configuration dictionary", function() + local resty_configuration_data_set = ngx.shared.configuration_data.set + before_each(function() + ngx.shared.configuration_data.set = function(key, value) return false, "" end + end) + + teardown(function() + ngx.shared.configuration_data.set = resty_configuration_data_set + end) + + it("returns a status of 400", function() + assert.has_no.errors(configuration.call) + assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST) + end) + + it("logs 'dynamic-configuration: error updating configuration:' to stderr", function() + local s = spy.on(ngx, "log") + assert.has_no.errors(configuration.call) + assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: error updating configuration: ") + end) + end) + + context("Succeeded to update backends configuration", function() + it("returns a status of 201", function() + assert.has_no.errors(configuration.call) + assert.equal(ngx.status, ngx.HTTP_CREATED) + end) + end) + end) + end) + + describe("handle_servers()", function() + local UUID = "2ea8adb5-8ebb-4b14-a79b-0cdcd892e884" + 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) - describe("handle_servers()", function() - it("should not accept non POST methods", function() - ngx.var.request_method = "GET" + it("should successfully update certificates and keys for each host", function() + ngx.var.request_method = "POST" + local mock_ssl_configuration = cjson.encode({ + servers = { ["hostname"] = UUID }, + certificates = { [UUID] = "pemCertKey" } + }) + ngx.req.get_body_data = function() return mock_ssl_configuration end - 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.set = function(self, data) return false, "error", nil 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("logs a warning when entry is forcibly stored", function() - local stored_entries = {} - - ngx.var.request_method = "POST" - ngx.shared.certificate_data.set = function(self, key, value) - stored_entries[key] = value - return true, nil, true - 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") - assert.has_no.errors(configuration.handle_servers) - assert.spy(s1).was_called_with(ngx.WARN, "certificate_data dictionary is full, LRU entry has been removed to store hostname") - assert.equal("pemCertKey", stored_entries["hostname"]) - assert.equal("pemCertKey2", stored_entries["hostname2"]) - assert.same(ngx.HTTP_CREATED, ngx.status) - end) + assert.has_no.errors(configuration.handle_servers) + assert.same(certificate_data:get(UUID), "pemCertKey") + assert.same(certificate_servers:get("hostname"), UUID) + 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() + local uuid2 = "8ea8adb5-8ebb-4b14-a79b-0cdcd892e999" + ngx.var.request_method = "POST" + ngx.shared.certificate_data.set = function(self, uuid, certificate) + return false, "error", nil + end + + local mock_ssl_configuration = cjson.encode({ + servers = { ["hostname"] = UUID, ["hostname2"] = uuid2 }, + certificates = { [UUID] = "pemCertKey", [uuid2] = "pemCertKey2" } + }) + ngx.req.get_body_data = function() return mock_ssl_configuration end + + local s = spy.on(ngx, "log") + assert.has_no.errors(configuration.handle_servers) + assert.spy(s).was_called_with(ngx.ERR, + string.format("error setting certificate for %s: error\nerror setting certificate for %s: error\n", UUID, uuid2)) + assert.same(ngx.status, ngx.HTTP_INTERNAL_SERVER_ERROR) + end) + + it("logs a warning when entry is forcibly stored", function() + local uuid2 = "8ea8adb5-8ebb-4b14-a79b-0cdcd892e999" + local stored_entries = {} + + ngx.var.request_method = "POST" + ngx.shared.certificate_data.set = function(self, uuid, certificate) + stored_entries[uuid] = certificate + return true, nil, true + end + local mock_ssl_configuration = cjson.encode({ + servers = { ["hostname"] = UUID, ["hostname2"] = uuid2 }, + certificates = { [UUID] = "pemCertKey", [uuid2] = "pemCertKey2" } + }) + + ngx.req.get_body_data = function() return mock_ssl_configuration end + + local s1 = spy.on(ngx, "log") + assert.has_no.errors(configuration.handle_servers) + assert.spy(s1).was_called_with(ngx.WARN, string.format("certificate_data dictionary is full, LRU entry has been removed to store %s", UUID)) + assert.equal("pemCertKey", stored_entries[UUID]) + assert.equal("pemCertKey2", stored_entries[uuid2]) + assert.same(ngx.HTTP_CREATED, ngx.status) + end) + end) end)