Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(healthcheck) support to mTLS in active checks #41

Merged
merged 2 commits into from
Jun 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

sudo: required

language: c

compiler: gcc
Expand Down Expand Up @@ -51,6 +49,7 @@ install:
- export PATH=$OPENRESTY_PREFIX/nginx/sbin:$LUAROCKS_PREFIX/bin:$PATH
- sudo luarocks install luacheck > build.log 2>&1 || (cat build.log && exit 1)
- sudo luarocks install lua-resty-worker-events > build.log 2>&1 || (cat build.log && exit 1)
- sudo luarocks install penlight > build.log 2>&1 || (cat build.log && exit 1)
- luarocks --version
- nginx -V

Expand Down
32 changes: 30 additions & 2 deletions lib/resty/healthcheck.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ local resty_lock = require ("resty.lock")
local re_find = ngx.re.find
local bit = require("bit")
local ngx_now = ngx.now
local ssl = require("ngx.ssl")

-- constants
local EVENT_SOURCE_PREFIX = "lua-resty-healthcheck"
Expand Down Expand Up @@ -828,14 +829,23 @@ function checker:run_single_check(ip, port, hostname, hostheader)
end

if self.checks.active.type == "https" then
local session
session, err = sock:sslhandshake(nil, hostname,
local session, err
if self.ssl_cert and self.ssl_key then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we don't need to branch here, because the ssl_cert and ssl_key are always set together. If both are not set then the client cert is not used anyway. The call to sock:sslhandshake is unnecessary for the same reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to keep sock:sslhandshake as a backward compatibility, as sock:tlshandshake is not available when the certificate is invalid, like this: TEST 2: active probes, invalid cert

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, sock:tlshandshake should work with or without a valid certificate. Could you elaborate?

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)
return self:report_tcp_failure(ip, port, hostname, "connect", "active")
end

end

local path = self.checks.active.http_path
Expand Down Expand Up @@ -1274,6 +1284,8 @@ end
-- * `name`: name of the health checker
-- * `shm_name`: the name of the `lua_shared_dict` specified in the Nginx configuration to use
-- * `checks.active.type`: "http", "https" or "tcp" (default is "http")
-- * `ssl_cert`: certificate for mTLS connections (string or parsed object)
-- * `ssl_key`: key for mTLS connections (string or parsed object)
-- * `checks.active.timeout`: socket timeout for active checks (in seconds)
-- * `checks.active.concurrency`: number of targets to check concurrently
-- * `checks.active.http_path`: path to use in `GET` HTTP request to run on active checks
Expand Down Expand Up @@ -1339,6 +1351,22 @@ 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
locao marked this conversation as resolved.
Show resolved Hide resolved
if type(opts.ssl_cert) == "cdata" then
self.ssl_cert = opts.ssl_cert
else
self.ssl_cert = assert(ssl.parse_pem_cert(opts.ssl_cert))
end

if type(opts.ssl_key) == "cdata" then
self.ssl_key = opts.ssl_key
else
self.ssl_key = assert(ssl.parse_pem_priv_key(opts.ssl_key))
end

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)
Expand Down
1 change: 1 addition & 0 deletions lua-resty-healthcheck-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ description = {
}
dependencies = {
"lua-resty-worker-events == 0.3.1",
"penlight == 1.7.0",
}
build = {
type = "builtin",
Expand Down
128 changes: 128 additions & 0 deletions t/17-mtls.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use Test::Nginx::Socket::Lua;
use Cwd qw(cwd);

workers(1);

plan tests => repeat_each() * 4;

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 pl_file = require "pl.file"
local cert = pl_file.read("t/util/cert.pem", true)
local key = pl_file.read("t/util/key.pem", true)

local healthcheck = require("resty.healthcheck")
local checker = healthcheck.new({
name = "testing_mtls",
shm_name = "test_shm",
type = "http",
ssl_cert = cert,
ssl_key = key,
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: configure a MTLS probe with parsed cert/key
--- 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 pl_file = require "pl.file"
local ssl = require "ngx.ssl"
local cert = ssl.parse_pem_cert(pl_file.read("t/util/cert.pem", true))
local key = ssl.parse_pem_priv_key(pl_file.read("t/util/key.pem", true))

local healthcheck = require("resty.healthcheck")
local checker = healthcheck.new({
name = "testing_mtls",
shm_name = "test_shm",
type = "http",
ssl_cert = cert,
ssl_key = key,
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
19 changes: 19 additions & 0 deletions t/util/cert.pem
Original file line number Diff line number Diff line change
@@ -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-----
28 changes: 28 additions & 0 deletions t/util/key.pem
Original file line number Diff line number Diff line change
@@ -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-----