From 925e402083fc2534f61d5a96b806b6816f6d203b Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Mon, 1 Oct 2018 16:21:30 -0300 Subject: [PATCH 1/4] feat(schema) adds `sni` typedef validating hostnames Introduces `typedefs.sni` for verifying the validity of hostnames. --- kong/db/schema/typedefs.lua | 24 +++++++++++++++++++ .../01-schema/03-typedefs_spec.lua | 19 ++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/kong/db/schema/typedefs.lua b/kong/db/schema/typedefs.lua index a5ff6fd629a8..6b976f70226e 100644 --- a/kong/db/schema/typedefs.lua +++ b/kong/db/schema/typedefs.lua @@ -57,6 +57,24 @@ local function validate_name(name) end +local function validate_sni(host) + local res, err_or_port = utils.normalize_ip(host) + if type(err_or_port) == "string" and err_or_port ~= "invalid port number" then + return nil, "invalid value: " .. host + end + + if res.type ~= "name" then + return nil, "must not be an IP" + end + + if err_or_port == "invalid port number" or type(res.port) == "number" then + return nil, "must not have a port" + end + + return true +end + + local typedefs = {} @@ -161,4 +179,10 @@ typedefs.name = Schema.define { } +typedefs.sni = Schema.define { + type = "string", + custom_validator = validate_sni, +} + + return typedefs diff --git a/spec/01-unit/000-new-dao/01-schema/03-typedefs_spec.lua b/spec/01-unit/000-new-dao/01-schema/03-typedefs_spec.lua index b2fe074511a1..623db796f065 100644 --- a/spec/01-unit/000-new-dao/01-schema/03-typedefs_spec.lua +++ b/spec/01-unit/000-new-dao/01-schema/03-typedefs_spec.lua @@ -5,7 +5,20 @@ local typedefs = require("kong.db.schema.typedefs") describe("typedefs", function() local a_valid_uuid = "cbb297c0-a956-486d-ad1d-f9b42df9465a" local a_blank_uuid = "00000000-0000-0000-0000-000000000000" - + + it("features sni typedef", function() + local Test = Schema.new({ + fields = { + { f = typedefs.sni } + } + }) + assert.truthy(Test:validate({ f = "example.com" })) + assert.truthy(Test:validate({ f = "9foo.te-st.bar.test" })) + assert.falsy(Test:validate({ f = "127.0.0.1" })) + assert.falsy(Test:validate({ f = "example.com:80" })) + assert.falsy(Test:validate({ f = "[::1]" })) + end) + it("features port typedef", function() local Test = Schema.new({ fields = { @@ -19,7 +32,7 @@ describe("typedefs", function() assert.falsy(Test:validate({ f = 65536 })) assert.falsy(Test:validate({ f = 65536.1 })) end) - + it("features protocol typedef", function() local Test = Schema.new({ fields = { @@ -77,7 +90,7 @@ describe("typedefs", function() local data = Test:process_auto_fields({}) assert.truthy(Test:validate(data)) assert.same(data.f, 120) - + data = Test:process_auto_fields({ f = 900 }) assert.truthy(Test:validate(data)) assert.same(data.f, 900) From b8d260815ac335bb78e333351203b6d0cda41ed6 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Fri, 28 Sep 2018 14:44:34 -0300 Subject: [PATCH 2/4] chore(deps) add lua-http dependency --- kong-0.14.1-0.rockspec | 1 + 1 file changed, 1 insertion(+) diff --git a/kong-0.14.1-0.rockspec b/kong-0.14.1-0.rockspec index d37f526c8cb0..5c9e28f8421d 100644 --- a/kong-0.14.1-0.rockspec +++ b/kong-0.14.1-0.rockspec @@ -23,6 +23,7 @@ dependencies = { "lua-cassandra == 1.3.2", "pgmoon == 1.8.0", "luatz == 0.3", + "http == 0.2", "lua_system_constants == 0.1.2", "lua-resty-iputils == 0.3.0", "luaossl == 20180708", From ea8568ab4b34ec8edf502aee7a242555ba449462 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Fri, 28 Sep 2018 14:46:35 -0300 Subject: [PATCH 3/4] chore(deps) bump lua-resty-healthchecks to 0.6.0 Adds support to `https_verify_certificate` field. --- kong-0.14.1-0.rockspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kong-0.14.1-0.rockspec b/kong-0.14.1-0.rockspec index 5c9e28f8421d..bf94fea76614 100644 --- a/kong-0.14.1-0.rockspec +++ b/kong-0.14.1-0.rockspec @@ -32,7 +32,7 @@ dependencies = { "lua-resty-dns-client == 2.2.0", "lua-resty-worker-events == 0.3.3", "lua-resty-mediador == 0.1.2", - "lua-resty-healthcheck == 0.5.0", + "lua-resty-healthcheck == 0.6.0", "lua-resty-cookie == 0.1.0", "lua-resty-mlcache == 2.2.0", -- external Kong plugins From 20011ddab1209215140ee426c777aa14fb8f5998 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Fri, 28 Sep 2018 21:43:21 -0300 Subject: [PATCH 4/4] feat(healthchecks) add support for HTTPS in active health checks * Introduces three new fields to `active` healthchecks configuration: * `type` ("tcp", "http" or "https", mirrorring lua-resty-healthcheck) * `https_verify_certificate` (boolean, mirrorring lua-resty-healthcheck) * `https_sni` - explicitly give an SNI, overriding the hostname configured in the Target * Adds tests using HTTPS, based on lua-http * lua-http is used for HTTPS tests, the mock server based on LuaSocket remains used for HTTP tests for performance reasons (faster startup on separate threads which are repeatedly spawned for various short-lived tests) * This commit also updates the certs in spec/fixtures, which were expired --- kong/db/schema/entities/upstreams.lua | 44 +- .../01-schema/08-upstreams_spec.lua | 25 +- .../05-proxy/09-balancer_spec.lua | 384 ++++++++++-------- spec/fixtures/kong_spec.crt | 28 +- spec/fixtures/kong_spec.key | 31 +- 5 files changed, 310 insertions(+), 202 deletions(-) diff --git a/kong/db/schema/entities/upstreams.lua b/kong/db/schema/entities/upstreams.lua index cbe0d541475c..52d82a9fa00c 100644 --- a/kong/db/schema/entities/upstreams.lua +++ b/kong/db/schema/entities/upstreams.lua @@ -55,11 +55,30 @@ local header_name = Schema.define { } -local healthchecks_defaults = { +local check_type = Schema.define { + type = "string", + one_of = { "tcp", "http", "https" }, + default = "http", +} + + +local check_verify_certificate = Schema.define { + type = "boolean", + default = true, +} + + +local NO_DEFAULT = {} + + +local healthchecks_config = { active = { + type = "http", timeout = 1, concurrency = 10, http_path = "/", + https_sni = NO_DEFAULT, + https_verify_certificate = true, healthy = { interval = 0, -- 0 = probing disabled by default http_statuses = { 200, 302 }, @@ -75,6 +94,7 @@ local healthchecks_defaults = { }, }, passive = { + type = "http", healthy = { http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308 }, @@ -91,6 +111,7 @@ local healthchecks_defaults = { local types = { + type = check_type, timeout = seconds, concurrency = positive_int, interval = seconds, @@ -100,26 +121,37 @@ local types = { http_failures = positive_int_or_zero, http_path = typedefs.path, http_statuses = http_statuses, + https_sni = typedefs.sni, + https_verify_certificate = check_verify_certificate, } + local function gen_fields(tbl) local fields = {} local count = 0 for name, default in pairs(tbl) do local typ = types[name] - local def + local def, required + if default == NO_DEFAULT then + default = nil + required = false + tbl[name] = nil + end if typ then - def = typ{ default = default } + def = typ{ default = default, required = required } else def = { type = "record", fields = gen_fields(default), default = default } end count = count + 1 fields[count] = { [name] = def } end - return fields + return fields, tbl end +local healthchecks_fields, healthchecks_defaults = gen_fields(healthchecks_config) + + local r = { name = "upstreams", primary_key = { "id" }, @@ -137,9 +169,9 @@ local r = { { slots = { type = "integer", default = 10000, between = { 10, 2^16 }, }, }, { healthchecks = { type = "record", default = healthchecks_defaults, - fields = gen_fields(healthchecks_defaults), + fields = healthchecks_fields, }, }, -}, + }, entity_checks = { -- hash_on_header must be present when hashing on header { conditional = { diff --git a/spec/01-unit/000-new-dao/01-schema/08-upstreams_spec.lua b/spec/01-unit/000-new-dao/01-schema/08-upstreams_spec.lua index 6209355d4176..43b0d5f01f5c 100644 --- a/spec/01-unit/000-new-dao/01-schema/08-upstreams_spec.lua +++ b/spec/01-unit/000-new-dao/01-schema/08-upstreams_spec.lua @@ -152,9 +152,11 @@ describe("load upstreams", function() assert.same(u.slots, 10000) assert.same(u.healthchecks, { active = { + type = "http", timeout = 1, concurrency = 10, http_path = "/", + https_verify_certificate = true, healthy = { interval = 0, http_statuses = { 200, 302 }, @@ -170,6 +172,7 @@ describe("load upstreams", function() }, }, passive = { + type = "http", healthy = { http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308 }, @@ -221,6 +224,10 @@ describe("load upstreams", function() local zero_integer = "value should be between 0 and 2147483648" local status_code = "value should be between 100 and 999" local integer = "expected an integer" + local boolean = "expected a boolean" + local invalid_host = "invalid value: " + local invalid_host_port = "must not have a port" + local invalid_ip = "must not be an IP" local tests = { {{ active = { timeout = -1 }}, seconds }, {{ active = { timeout = 1e+42 }}, seconds }, @@ -229,6 +236,20 @@ describe("load upstreams", function() {{ active = { concurrency = -10 }}, pos_integer }, {{ active = { http_path = "" }}, "length must be at least 1" }, {{ active = { http_path = "ovo" }}, "should start with: /" }, + {{ active = { https_sni = "127.0.0.1", }}, invalid_ip }, + {{ active = { https_sni = "127.0.0.1:8080", }}, invalid_ip }, + {{ active = { https_sni = "/example", }}, invalid_host }, + {{ active = { https_sni = ".example", }}, invalid_host }, + {{ active = { https_sni = "example.", }}, invalid_host }, + {{ active = { https_sni = "example:", }}, invalid_host }, + {{ active = { https_sni = "mock;bin", }}, invalid_host }, + {{ active = { https_sni = "example.com/org", }}, invalid_host }, + {{ active = { https_sni = "example-.org", }}, invalid_host }, + {{ active = { https_sni = "example.org-", }}, invalid_host }, + {{ active = { https_sni = "hello..example.com", }}, invalid_host }, + {{ active = { https_sni = "hello-.example.com", }}, invalid_host }, + {{ active = { https_sni = "example.com:1234", }}, invalid_host_port }, + {{ active = { https_verify_certificate = "ovo", }}, boolean }, {{ active = { healthy = { interval = -1 }}}, seconds }, {{ active = { healthy = { interval = 1e+42 }}}, seconds }, {{ active = { healthy = { http_statuses = 404 }}}, "expected an array" }, @@ -292,7 +313,7 @@ describe("load upstreams", function() repeat leaf = leaf[next(leaf)] until type(leaf) ~= "table" or type(next(leaf)) ~= "string" - assert.equal(test[2], leaf, inspect(err)) + assert.match(test[2], leaf, 1, true, inspect(err)) end end) @@ -304,6 +325,8 @@ describe("load upstreams", function() { active = { concurrency = 2 }}, { active = { http_path = "/" }}, { active = { http_path = "/test" }}, + { active = { https_sni = "example.com" }}, + { active = { https_verify_certificate = false }}, { active = { healthy = { interval = 0 }}}, { active = { healthy = { http_statuses = { 200, 300 } }}}, { active = { healthy = { successes = 2 }}}, diff --git a/spec/02-integration/05-proxy/09-balancer_spec.lua b/spec/02-integration/05-proxy/09-balancer_spec.lua index 5ca5f2b781c9..739be869b12d 100644 --- a/spec/02-integration/05-proxy/09-balancer_spec.lua +++ b/spec/02-integration/05-proxy/09-balancer_spec.lua @@ -103,7 +103,7 @@ end -- odd entries are 200s, event entries are 500s -- @param test_log (optional, default fals) Produce detailed logs -- @return Returns the number of succesful and failure responses. -local function http_server(host, port, counts, test_log) +local function http_server(host, port, counts, test_log, protocol) -- This is a "hard limit" for the execution of tests that launch -- the custom http_server @@ -112,10 +112,10 @@ local function http_server(host, port, counts, test_log) local threads = require "llthreads2.ex" local thread = threads.new({ - function(expire, host, port, counts, TEST_LOG) - local TIMEOUT = -1 + function(expire, host, port, counts, TEST_LOG, protocol) -- luacheck: ignore + local TIMEOUT = -1 -- luacheck: ignore - local function test_log(...) + local function test_log(...) -- luacheck: ignore if not TEST_LOG then return end @@ -127,91 +127,139 @@ local function http_server(host, port, counts, test_log) print(table.concat(t)) end - local socket = require "socket" - local server - if host:match(":") then - server = assert(socket.tcp6()) - else - server = assert(socket.tcp()) + local total_reqs = 0 + for _, c in pairs(counts) do + total_reqs = total_reqs + (c < 0 and 1 or c) end - assert(server:setoption('reuseaddr', true)) - assert(server:bind("*", port)) - assert(server:listen()) local handshake_done = false - - assert(server:settimeout(0.5)) - test_log("started") - + local fail_responses = 0 + local ok_responses = 0 + local reply_200 = true local healthy = true local n_checks = 0 - - local ok_responses, fail_responses = 0, 0 - local total_reqs = 0 - for _, c in pairs(counts) do - total_reqs = total_reqs + (c < 0 and 1 or c) - end local n_reqs = 0 - local reply_200 = true - while n_reqs < total_reqs + 1 do - local client, err - client, err = server:accept() - if socket.gettime() > expire then - client:close() - break - - elseif not client then - if err ~= "timeout" then - server:close() - error(err) - end - else - local lines = {} - local line, err - while #lines < 7 do - line, err = client:receive() - if err or #line == 0 then - break - else - table.insert(lines, line) + local httpserver, get_path, send_response, sleep, sslctx + if protocol == "https" then + -- lua-http server for http+https tests + local cqueues = require "cqueues" + sleep = cqueues.sleep + httpserver = require "http.server" + local openssl_pkey = require "openssl.pkey" + local openssl_x509 = require "openssl.x509" + get_path = function(stream) + local headers = assert(stream:get_headers(60)) + return headers:get(":path") + end + local httpheaders = require "http.headers" + send_response = function(stream, response) + local rh = httpheaders.new() + rh:upsert(":status", tostring(response)) + rh:upsert("connection", "close") + assert(stream:write_headers(rh, false)) + end + sslctx = require("http.tls").new_server_context() + local fd = io.open("spec/fixtures/kong_spec.key", "r") + local pktext = fd:read("*a") + fd:close() + local pk = assert(openssl_pkey.new(pktext)) + assert(sslctx:setPrivateKey(pk)) + fd = io.open("spec/fixtures/kong_spec.crt", "r") + local certtext = fd:read("*a") + fd:close() + local cert = assert(openssl_x509.new(certtext)) + assert(sslctx:setCertificate(cert)) + else + -- luasocket-based mock for http-only tests + -- not a real HTTP server, but runs faster + local socket = require "socket" + sleep = socket.sleep + httpserver = { + listen = function(opts) + local server = {} + local sskt + server.close = function() + server.quit = true end - end - if err and err ~= "closed" then - client:close() - server:close() - error(err) - end - if #lines == 0 then - goto continue - end - local got_handshake = lines[1] and lines[1]:match("/handshake") - if got_handshake then - client:send("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n") + server.loop = function(self) + while not self.quit do + local cskt, err = sskt:accept() + if socket.gettime() > expire then + cskt:close() + break + elseif err ~= "timeout" then + if err then + sskt:close() + error(err) + end + local first, err = cskt:receive("*l") + if first then + opts.onstream(server, { + get_path = function() + return (first:match("(/[^%s]*)")) + end, + send_response = function(_, response) + local r = response == 200 and "OK" or "Internal Server Error" + cskt:send("HTTP/1.1 " .. response .. " " .. r .. + "\r\nConnection: close\r\n\r\n") + end, + }) + end + cskt:close() + if err and err ~= "closed" then + sskt:close() + error(err) + end + end + end + sskt:close() + end + local socket_fn = host:match(":") and socket.tcp6 or socket.tcp + sskt = assert(socket_fn()) + assert(sskt:settimeout(0.1)) + assert(sskt:setoption('reuseaddr', true)) + assert(sskt:bind("*", opts.port)) + assert(sskt:listen()) + return server + end, + } + get_path = function(stream) + return stream:get_path() + end + send_response = function(stream, response) + return stream:send_response(response) + end + end + + local server = httpserver.listen({ + host = host:gsub("[%]%[]", ""), + port = port, + reuseaddr = true, + v6only = host:match(":") ~= nil, + ctx = sslctx, + onstream = function(self, stream) + local path = get_path(stream) + local response = 200 + local shutdown = false + + if path == "/handshake" then handshake_done = true - elseif lines[1]:match("/shutdown") then - client:send("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n") - client:close() - break + elseif path == "/shutdown" then + shutdown = true - elseif lines[1]:match("/status") then - if healthy then - client:send("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n") - else - client:send("HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n") - end + elseif path == "/status" then + response = healthy and 200 or 500 n_checks = n_checks + 1 - elseif lines[1]:match("/healthy") then + elseif path == "/healthy" then healthy = true - client:send("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n") - elseif lines[1]:match("/unhealthy") then + elseif path == "/unhealthy" then healthy = false - client:send("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n") - elseif handshake_done and not got_handshake then + elseif handshake_done then n_reqs = n_reqs + 1 test_log("nreqs ", n_reqs, " of ", total_reqs) @@ -224,41 +272,34 @@ local function http_server(host, port, counts, test_log) end if counts[1] == TIMEOUT then counts[1] = 0 - socket.sleep(0.2) + sleep(0.2) elseif counts[1] > 0 then counts[1] = counts[1] - 1 end - - local response - if reply_200 then - response = "HTTP/1.1 200 OK" + response = reply_200 and 200 or 500 + if response == 200 then + ok_responses = ok_responses + 1 else - response = "HTTP/1.1 500 Internal Server Error" - end - local sent = client:send(response .. "\r\nConnection: close\r\n\r\n") - if sent then - if reply_200 then - ok_responses = ok_responses + 1 - else - fail_responses = fail_responses + 1 - end + fail_responses = fail_responses + 1 end else - client:close() - server:close() - error("got a request before the handshake was complete") + error("got a request before handshake was complete") end - client:close() - test_log(ok_responses, " oks, ", fail_responses," fails handled") - end - ::continue:: - end - server:close() - test_log("closed") + + send_response(stream, response) + + if shutdown then + self:close() + end + end, + }) + test_log("starting") + server:loop() + test_log("stopped") return ok_responses, fail_responses, n_checks end - }, expire, host, port, counts, test_log or TEST_LOG) + }, expire, host, port, counts, test_log or TEST_LOG, protocol) local server = thread:start() @@ -486,13 +527,13 @@ local function wait_for_router_update(old_rv, localhost, proxy_port, admin_port) local dummy_upstream_name = add_upstream() local dummy_port = add_target(dummy_upstream_name, localhost) local dummy_api_host = add_api(dummy_upstream_name) - local dummy_server = http_server(localhost, dummy_port, { math.huge }) + local dummy_server = http_server(localhost, dummy_port, { 1000 }) helpers.wait_until(function() client_requests(1, dummy_api_host, "127.0.0.1", proxy_port) local rv = get_router_version(admin_port) return rv ~= old_rv - end, 10) + end, 5) dummy_server:done() end @@ -516,6 +557,7 @@ for _, strategy in helpers.each_strategy() do helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "../spec/fixtures/kong_spec.crt", db_update_frequency = 0.1, }) end) @@ -587,7 +629,7 @@ for _, strategy in helpers.each_strategy() do end) - it("propagates posted health info", function() + it("propagates posted health info #flaky", function() local old_rv = get_router_version(admin_port_2) @@ -660,6 +702,7 @@ for _, strategy in helpers.each_strategy() do local updated = { active = { + type = "http", concurrency = 10, healthy = { http_statuses = { 200, 302 }, @@ -667,6 +710,7 @@ for _, strategy in helpers.each_strategy() do successes = 1 }, http_path = "/status", + https_verify_certificate = true, timeout = 1, unhealthy = { http_failures = 1, @@ -677,6 +721,7 @@ for _, strategy in helpers.each_strategy() do } }, passive = { + type = "http", healthy = { http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308 }, @@ -977,82 +1022,85 @@ for _, strategy in helpers.each_strategy() do end end) - it("perform active health checks -- automatic recovery", function() - - for nchecks = 1, 3 do + for _, protocol in ipairs({"http", "https"}) do + it("perform active health checks -- automatic recovery #" .. protocol, function() + for nchecks = 1, 3 do - local port1 = gen_port() - local port2 = gen_port() + local port1 = gen_port() + local port2 = gen_port() - -- setup target servers: - -- server2 will only respond for part of the test, - -- then server1 will take over. - local server1_oks = SLOTS * 2 - local server2_oks = SLOTS - local server1 = http_server(localhost, port1, { server1_oks }) - local server2 = http_server(localhost, port2, { server2_oks }) + -- setup target servers: + -- server2 will only respond for part of the test, + -- then server1 will take over. + local server1_oks = SLOTS * 2 + local server2_oks = SLOTS + local server1 = http_server(localhost, port1, { server1_oks }, nil, protocol) + local server2 = http_server(localhost, port2, { server2_oks }, nil, protocol) - -- configure healthchecks - local upstream_name = add_upstream({ - healthchecks = healthchecks_config { - active = { - http_path = "/status", - healthy = { - interval = HEALTHCHECK_INTERVAL, - successes = nchecks, - }, - unhealthy = { - interval = HEALTHCHECK_INTERVAL, - http_failures = nchecks, - }, + -- configure healthchecks + local upstream_name = add_upstream({ + healthchecks = healthchecks_config { + active = { + type = protocol, + http_path = "/status", + https_verify_certificate = (protocol == "https" and localhost == "localhost"), + healthy = { + interval = HEALTHCHECK_INTERVAL, + successes = nchecks, + }, + unhealthy = { + interval = HEALTHCHECK_INTERVAL, + http_failures = nchecks, + }, + } } - } - }) - add_target(upstream_name, localhost, port1) - add_target(upstream_name, localhost, port2) - local api_host = add_api(upstream_name) - - -- 1) server1 and server2 take requests - local oks, fails = client_requests(SLOTS, api_host) + }) + add_target(upstream_name, localhost, port1) + add_target(upstream_name, localhost, port2) + local api_host = add_api(upstream_name) - -- server2 goes unhealthy - direct_request(localhost, port2, "/unhealthy") - -- Wait until healthchecker detects - poll_wait_health(upstream_name, localhost, port2, "UNHEALTHY") + -- 1) server1 and server2 take requests + local oks, fails = client_requests(SLOTS, api_host) + + -- server2 goes unhealthy + direct_request(localhost, port2, "/unhealthy") + -- Wait until healthchecker detects + poll_wait_health(upstream_name, localhost, port2, "UNHEALTHY") + + -- 2) server1 takes all requests + do + local o, f = client_requests(SLOTS, api_host) + oks = oks + o + fails = fails + f + end + + -- server2 goes healthy again + direct_request(localhost, port2, "/healthy") + -- Give time for healthchecker to detect + poll_wait_health(upstream_name, localhost, port2, "HEALTHY") + + -- 3) server1 and server2 take requests again + do + local o, f = client_requests(SLOTS, api_host) + oks = oks + o + fails = fails + f + end - -- 2) server1 takes all requests - do - local o, f = client_requests(SLOTS, api_host) - oks = oks + o - fails = fails + f - end + -- collect server results; hitcount + local _, ok1, fail1 = server1:done() + local _, ok2, fail2 = server2:done() - -- server2 goes healthy again - direct_request(localhost, port2, "/healthy") - -- Give time for healthchecker to detect - poll_wait_health(upstream_name, localhost, port2, "HEALTHY") + -- verify + assert.are.equal(SLOTS * 2, ok1) + assert.are.equal(SLOTS, ok2) + assert.are.equal(0, fail1) + assert.are.equal(0, fail2) - -- 3) server1 and server2 take requests again - do - local o, f = client_requests(SLOTS, api_host) - oks = oks + o - fails = fails + f + assert.are.equal(SLOTS * 3, oks) + assert.are.equal(0, fails) end - - -- collect server results; hitcount - local _, ok1, fail1 = server1:done() - local _, ok2, fail2 = server2:done() - - -- verify - assert.are.equal(SLOTS * 2, ok1) - assert.are.equal(SLOTS, ok2) - assert.are.equal(0, fail1) - assert.are.equal(0, fail2) - - assert.are.equal(SLOTS * 3, oks) - assert.are.equal(0, fails) - end - end) + end) + end it("perform active health checks -- can detect before any proxy traffic", function() @@ -1093,6 +1141,7 @@ for _, strategy in helpers.each_strategy() do helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "../spec/fixtures/kong_spec.crt", db_update_frequency = 0.1, }) @@ -1100,6 +1149,7 @@ for _, strategy in helpers.each_strategy() do poll_wait_health(upstream_name, localhost, port2, "UNHEALTHY") -- server1 takes all requests + local client_oks, client_fails = client_requests(requests, api_host) -- collect server results; hitcount diff --git a/spec/fixtures/kong_spec.crt b/spec/fixtures/kong_spec.crt index 1574c79d2984..773f85e8abc2 100644 --- a/spec/fixtures/kong_spec.crt +++ b/spec/fixtures/kong_spec.crt @@ -1,15 +1,17 @@ -----BEGIN CERTIFICATE----- -MIICazCCAdQCCQCK2pI8gFEqZjANBgkqhkiG9w0BAQsFADB6MQswCQYDVQQGEwJV -UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzES -MBAGA1UECgwJS29uZyBTcGVjMRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50MRIwEAYD -VQQDDAlsb2NhbGhvc3QwHhcNMTYwNzA3MTcyOTE1WhcNMTYwODA2MTcyOTE1WjB6 -MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu -IEZyYW5jaXNjbzESMBAGA1UECgwJS29uZyBTcGVjMRYwFAYDVQQLDA1JVCBEZXBh -cnRtZW50MRIwEAYDVQQDDAlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0A -MIGJAoGBAN57ttZYKc9QAWGh82s8LuTFAn6UqzoSsQn9+qBMBCR07YySLF7H6y02 -0662IBhs+xW2X9R1baYMCGP5lL65IPiGHT6Ut4IJ2ofqsx87QeaYRL1OLk7XyHVZ -XeqS52OL+dkJX0IWG+T4ot6pbsT/dZNIOAlmsM+2gMREGZLSzyMJAgMBAAEwDQYJ -KoZIhvcNAQELBQADgYEAh2EDZt3xZ6GRKQRcw5ZsTWZqHoV7oXA3CYDZHc7C6kVx -SqrU19cBwkGKnG/Y1fYQXw6MULoYzOvwDkhTTONAnPUR2V5NdYHm+h0Jz1rLsQnA -/4aLS9sSvt1BxPoovfspdNYAkGzmRevRhemEBem95fWVbk/cqV4xdzh4Pu6sREU= +MIICwTCCAiqgAwIBAgIJAOloHn/ZJQw8MA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMRIwEAYDVQQKDAlLb25nIFNwZWMxFDASBgNVBAsMC0VuZ2luZWVyaW5nMRIw +EAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgwOTI2MTQ0MTE2WhcNMzgwOTIxMTQ0MTE2 +WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN +U2FuIEZyYW5jaXNjbzESMBAGA1UECgwJS29uZyBTcGVjMRQwEgYDVQQLDAtFbmdp +bmVlcmluZzESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQC/QqEXsZFIY/hHqtMro+hkhHwbydvPHGhe/rqiOHXUVGnjJ9bBgrk6 +iLFnu7L0OxsMPdckxjCLFYO2nGERlAN1wXxw0cLLUF0v1sOhJT+57pBfTgmfzLvp +iLLOWMhayRcjZWJdHGcKUG3xh6o8MghdZIVoewlyqzViRXvR3U1VYwIDAQABo1Mw +UTAdBgNVHQ4EFgQUgIzN48PLQMbLWg4muR2QZqhJXicwHwYDVR0jBBgwFoAUgIzN +48PLQMbLWg4muR2QZqhJXicwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsF +AAOBgQArRbblXU7zocrHf5rVrFTWJsaA2aSSJ4CmY1SGRQVyAUzyJXX+Koe+qkP/ +iEnA4TUWZfGQOkN5E8ybUxOWv7+6GBjMooLf7WAj5TCtyfOSFASIBrFNGevL4GgH +J65KdKlncizFAjSxk1KMRBXMGYDyeBGHARvAVKaknDTD7CReEg== -----END CERTIFICATE----- diff --git a/spec/fixtures/kong_spec.key b/spec/fixtures/kong_spec.key index c6a7b3201551..bc53f87f55db 100644 --- a/spec/fixtures/kong_spec.key +++ b/spec/fixtures/kong_spec.key @@ -1,15 +1,16 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDee7bWWCnPUAFhofNrPC7kxQJ+lKs6ErEJ/fqgTAQkdO2Mkixe -x+stNtOutiAYbPsVtl/UdW2mDAhj+ZS+uSD4hh0+lLeCCdqH6rMfO0HmmES9Ti5O -18h1WV3qkudji/nZCV9CFhvk+KLeqW7E/3WTSDgJZrDPtoDERBmS0s8jCQIDAQAB -AoGBAMmaHTHZrfk3rCjGUgcn/+45P2toWEhFS7ucM5ExkMdgVSl+A0rXqZnNBsBY -NHktt9AS9H9W8Ub3vFqrfEffBam9c7onxTLfGtD8iupYZzYnrMEp+Yevod2J5NRd -1mdDpcdSY/z88QMC3ug+e+azpDywWYyavc7eqVBrh1iuO8sBAkEA82tRwOwDfE0w -14GrAtHUpSPSqAgYCRxRGJHXutFgcBfeNhSop7K46WiogptbfwW3CmJ0jLAKUAn8 -1y1RApTdkQJBAOn7ZCBDJnTes4+r9DOsBaSXQtpiYqs2wQdTAi8x5ey6F57iR/iF -BRuXT5rqHb77efhwNrG63EwOK0A6zqX9EfkCQEvSfA659YP8j2AxldE1ByXBeNa6 -XrJHzcL3etlIat+/r4ns+XPzKcIaxZKZTfDNGOz1KhTAqRWxZr6n8Pn64OECQBEX -NroQjZeNyMnewoifsQ3TqqNu+kwNRM43Jvqxh1ziPuVxG9awSK46QCtNnHydu8wE -zknvHzZC9q/LW9rOAGkCQDIf5n3BoG0ZywMFCZ1SyOtuEa84fzagoxxvRN28wuJ1 -iYn1xSebLiVdOWhHi4XPi+zz2hK2VshZoEL7h4iF9BQ= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL9CoRexkUhj+Eeq +0yuj6GSEfBvJ288caF7+uqI4ddRUaeMn1sGCuTqIsWe7svQ7Gww91yTGMIsVg7ac +YRGUA3XBfHDRwstQXS/Ww6ElP7nukF9OCZ/Mu+mIss5YyFrJFyNlYl0cZwpQbfGH +qjwyCF1khWh7CXKrNWJFe9HdTVVjAgMBAAECgYAZn4eNcRCRrjL5Bv27fv4HWWh3 +IJf+K0QgVegTC5VdmOGGuTOgQS8nlGCQESlsZu68uRw1pQej2oIG2PR4Mmg0Bvkv +XKYdO0TY98nWIjWSrR6y/Yt/RoiXDfLa8d0cqb734kh7kPuQHpCCpEWcKRMb8jdp +LIpS21TVUkUz4LwYIQJBAO+4PlDVV2r54VYaJLeSv5qHMYna57b+mGaHC0SMKav4 +np7OxWhCEkXzQXO4Qjnqimr96pGlbOnFZbpAVw+S1GUCQQDMP99LPD0oJ9Gw+y/N +Ud1xRQGOk8/vQYcO/PfGNXhoPST8rnraimdb+/5t+alM7UbdcMRbWgKO8j3h0FoX +UnInAkBUid0wFIynpUfaXY3lT1NS46qMuy5MUqzcO3O10NhBVYRa7QChK+vVz1ud +u7VfR19ZLAK1KmmmZ37gmCAb1eQhAkEAvQM+uHj+f3KZ8pYBHphrvK6HSlIvUtHp +Ek23XY2N56jt2Yf92M/L5qvEQDGSIsZRlgsNKxyY0YALFDWjqYF6cQJAfDJi8Uya +jc9UZeMLtQi3eLugwB/Qx6p1xhjj358Hhprg7rFtqbZNA0IHJYuTx6W+2CXT2GpW +t842/FWkibh8xg== +-----END PRIVATE KEY-----