diff --git a/lib/resty/healthcheck.lua b/lib/resty/healthcheck.lua index 739301b1..1d7daa19 100644 --- a/lib/resty/healthcheck.lua +++ b/lib/resty/healthcheck.lua @@ -38,6 +38,8 @@ local resty_lock = require ("resty.lock") local re_find = ngx.re.find local bit = require("bit") local ngx_now = ngx.now +local pl_file = require("pl.file") +local ssl = require("ngx.ssl") -- constants local EVENT_SOURCE_PREFIX = "lua-resty-healthcheck" @@ -829,8 +831,16 @@ function checker:run_single_check(ip, port, hostname, hostheader) if self.checks.active.type == "https" then local session - session, err = sock:sslhandshake(nil, hostname, + if self.ssl_cert and self.ssl_key then + session, err = sock:tlshandshake({ + verify = self.checks.active.https_verify_certificate, + client_cert = self.ssl_cert, + client_priv_key = self.ssl_key + }) + else + session, err = sock:sslhandshake(nil, hostname, self.checks.active.https_verify_certificate) + end if not session then sock:close() self:log(ERR, "failed SSL handshake with '", hostname, " (", ip, ":", port, ")': ", err) @@ -1339,6 +1349,12 @@ function _M.new(opts) self.shm = ngx.shared[tostring(opts.shm_name)] assert(self.shm, ("no shm found by name '%s'"):format(opts.shm_name)) + -- load certificate and key + if opts.ssl_cert and opts.ssl_key then + self.ssl_cert = assert(ssl.parse_pem_cert(pl_file.read(opts.ssl_cert, true))) + self.ssl_key = assert(ssl.parse_pem_priv_key(pl_file.read(opts.ssl_key, true))) + end + -- other properties self.targets = nil -- list of targets, initially loaded, maintained by events self.events = nil -- hash table with supported events (prevent magic strings) diff --git a/t/17-mtls.t b/t/17-mtls.t new file mode 100644 index 00000000..ad0a2407 --- /dev/null +++ b/t/17-mtls.t @@ -0,0 +1,114 @@ +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +workers(1); + +plan tests => repeat_each() * 3; + +my $pwd = cwd(); + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;;"; + lua_shared_dict test_shm 8m; + lua_shared_dict my_worker_events 8m; +}; + +run_tests(); + +__DATA__ + +=== TEST 1: configure a MTLS probe +--- http_config eval +qq{ + $::HttpConfig +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing_mtls", + shm_name = "test_shm", + type = "http", + ssl_cert = "t/util/cert.pem", + ssl_key = "t/util/key.pem", + checks = { + active = { + http_path = "/status", + healthy = { + interval = 999, -- we don't want active checks + successes = 3, + }, + unhealthy = { + interval = 999, -- we don't want active checks + tcp_failures = 3, + http_failures = 3, + } + }, + passive = { + healthy = { + successes = 3, + }, + unhealthy = { + tcp_failures = 3, + http_failures = 3, + } + } + } + }) + ngx.say(checker ~= nil) -- true + } + } +--- request +GET /t +--- response_body +true + +=== TEST 2: fail to configure a MTLS probe +--- http_config eval +qq{ + $::HttpConfig +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing_mtls", + shm_name = "test_shm", + type = "http", + ssl_cert = "t/util/wrongcert.pem", + ssl_key = "t/util/wrongkey.pem", + checks = { + active = { + http_path = "/status", + healthy = { + interval = 999, -- we don't want active checks + successes = 3, + }, + unhealthy = { + interval = 999, -- we don't want active checks + tcp_failures = 3, + http_failures = 3, + } + }, + passive = { + healthy = { + successes = 3, + }, + unhealthy = { + tcp_failures = 3, + http_failures = 3, + } + } + } + }) + } + } +--- request +GET /t +--- error_code: 500 diff --git a/t/util/cert.pem b/t/util/cert.pem new file mode 100644 index 00000000..2df6a75a --- /dev/null +++ b/t/util/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUWWntedJ1yLAJE2baK/Mg06osmGAwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UECgwJS29uZyBJbmMuMB4XDTIwMDQyMzIwMjcwMFoXDTMwMDQy +MTIwMjcwMFowFDESMBAGA1UECgwJS29uZyBJbmMuMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAvVBrEH34MzwKlkBapiNyXr9huSShuojy+7i/01BSFng3 +1TiejXJ3pEjykZqt7ENkZ6+BTYUdb9klK221yXiSyX71x97O0WHHuhH/m4XwGiIH +YPBHdg+ExdMRflXgwtlW3of2hTWxkPkPQDPhoSQVMc5DkU7EOgrTxkv1rUWVAed4 +gSK4IT2AkhKwOSkewZANj2bnK5Evf71ACyJd7IQbJAIYoKBwRJAUXJMA7XAreIB+ +nEr9whNYTklhB4aEa2wtOQuiQubIMJzdOryEX5nufH+tL4p1QKhRPFAqqtJ2Czgw +YZY/v9IrThl19r0nL7FIvxFDNIMeOamJxDLQqsh9NwIDAQABo1MwUTAdBgNVHQ4E +FgQU9t6YAdQ5mOXeqvptN5l3yYZGibEwHwYDVR0jBBgwFoAU9t6YAdQ5mOXeqvpt +N5l3yYZGibEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhi83 +aXsfJGqr9Zb1guWxbI8uKoG6o88ptXjV2c6dJnxXag0A/Rj+bX2bcPkN2kvQksNl +MBUQlniOydZfsBUAoC0V7yyGUv9eO2RIeFnnNpRXNu+n+Kg2bvgvu8BKNNNOASZv ++Vmzvo9lbfhS9MNAxYk9eTiPNUZ3zn2RfFyT6YWWJbRjk//EAlchyud3XGug9/hw +c05dtzWEYT8GdzMd+Y1/2kR5r/CapSj7GEqL5T3+zDIfjbhTokV7WBrw6og2avoZ +vzrF8xWucry5/2mKQbRxMyCtKYUKTcoLzF4HrNQCETm0n9qUODrHER7Wit9fQFZX +1GEA3BkX2tsbIVVaig== +-----END CERTIFICATE----- diff --git a/t/util/key.pem b/t/util/key.pem new file mode 100644 index 00000000..ae945f44 --- /dev/null +++ b/t/util/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9UGsQffgzPAqW +QFqmI3Jev2G5JKG6iPL7uL/TUFIWeDfVOJ6NcnekSPKRmq3sQ2Rnr4FNhR1v2SUr +bbXJeJLJfvXH3s7RYce6Ef+bhfAaIgdg8Ed2D4TF0xF+VeDC2Vbeh/aFNbGQ+Q9A +M+GhJBUxzkORTsQ6CtPGS/WtRZUB53iBIrghPYCSErA5KR7BkA2PZucrkS9/vUAL +Il3shBskAhigoHBEkBRckwDtcCt4gH6cSv3CE1hOSWEHhoRrbC05C6JC5sgwnN06 +vIRfme58f60vinVAqFE8UCqq0nYLODBhlj+/0itOGXX2vScvsUi/EUM0gx45qYnE +MtCqyH03AgMBAAECggEAA1hWa/Yt2onnDfyZHXJm5PGwwlq5WNhuorADA7LZoHgD +VIspkgpBvu9jCduX0yLltUdOm5YMjRtjIr9PhP3SaikKIrv3H5AAvXLv90mIko2j +X70fJiDkEbLHDlpqHEdG16vDWVs3hf5AnLvN8tD2ZujkHL8tjHEAiPJyptsh5OSw +XaltCD67U940XXJ89x0zFZ/3RoRk78wX3ELz7/dY0cMnslMavON+LYTq9hQZyVmm +nOhZICWerKjax4t5f9PZ/zM6IhEVrUhw2WrC31tgRo+ITCIA/nkKid8vNhkiLVdw +jTyAYDLgYW7K8/zVrzmV9TOr3CaZHLQxnF/LMpIEAQKBgQDjnA/G4g2mDD7lsqU1 +N3it87v2VBnZPFNW6L17Qig+2BDTXg1kadFBlp8qtEJI+H5axVSmzsrlmATJVhUK +iYOQwiEsQnt4tGmWZI268NAIUtv0TX0i9yscsezmvGABMcyBCF7ZwFhUfhy0pn1t +kzmbYN4AjYdcisCnSusoMD92NwKBgQDU7YVNuieMIZCIuSxG61N1+ZyX3Ul5l6KU +m1xw1PZvugqXnQlOLV/4Iaz86Vvlt2aDqTWO/iv4LU7ixNdhRtxFIU/b2a8DzDOw +ijhzMGRJqJOdi1NfciiIWHyrjRmGbhCgm784vqV7qbQomiIsjgnDvjoZkossZMiJ +63vs7huxAQKBgQDiQjT8w6JFuk6cD+Zi7G2unmfvCtNXO7ys3Fffu3g+YJL5SrmN +ZBN8W7qFvQNXfo48tYTc/Rx8941qh4QLIYAD2rcXRE9xQgbkVbj+aHykiZnVVWJb +69CTidux0vist1BPxH5lf+tOsr7eZdKxpnTRnI2Thx1URSoWI0d4f93WKQKBgBXn +kW0bl3HtCgdmtU1ebCmY0ik1VJezp8AN84aQAgIga3KJbymhtVu7ayZhg1iwc1Vc +FOxu7WsMji75/QY+2e4qrSJ61GxZl3+z2HbRJaAGPZlZeew5vD26jKjBTTztGbzM +CPH3euKr5KLAqH9Y5VxDt4pl7vdULuUxWoBXRnYBAoGAHIFMYiCdXETtrFHKVTzc +vm4P24PnsNHoDTGMXPeRYRKF2+3VEJrwp1Q3fue4Go4zFB8I6nhNVIbh4dIHxFab +hyxZvGWGUgRvTvD4VYn/YHVoSf2/xNZ0r/S2LKomp+jwoWKfukbCoDjAOWvnK5iD +o41Tn0yhzBdnrYguKznGR3g= +-----END PRIVATE KEY-----