Skip to content
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
8 changes: 6 additions & 2 deletions apisix/healthcheck_manager.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ local function compute_targets(up_conf)
local host = up_conf.checks and up_conf.checks.active and up_conf.checks.active.host
local port = up_conf.checks and up_conf.checks.active and up_conf.checks.active.port
local up_hdr = up_conf.pass_host == "rewrite" and up_conf.upstream_host
local use_node_hdr = up_conf.pass_host == "node" or nil

local targets = {}
for _, node in ipairs(up_conf.nodes) do
local host_hdr = up_hdr or (use_node_hdr and node.domain) or nil
-- A health check has no client; the gateway probes the node by the node's own
-- identity (its domain), so use node.domain as the Host header regardless of
-- pass_host (except "rewrite", which pins an explicit Host). Otherwise a domain
-- node probes with the resolved ip as Host/SNI, breaking HTTPS health checks.
-- node.domain is nil for ip nodes.
local host_hdr = up_hdr or node.domain
local target_port = port or node.port
-- add_target defaults the hostname to the ip when checks.active.host is
-- unset, so mirror that here to match the shm entry's stored hostname
Expand Down
190 changes: 190 additions & 0 deletions t/node/healthcheck-node-domain-host.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';

repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();

add_block_preprocessor(sub {
my ($block) = @_;

if (!$block->http_config) {
# probe targets that log what the active health checker actually sends:
# the Host header over HTTP, and the TLS SNI over HTTPS.
my $http_config = <<'_EOC_';
server {
listen 1988;
location /healthz {
content_by_lua_block {
ngx.log(ngx.WARN, "probe Host: ", ngx.var.http_host)
ngx.say("ok")
}
}
}

server {
listen 1989 ssl;
ssl_certificate ../../certs/apisix.crt;
ssl_certificate_key ../../certs/apisix.key;
location /healthz {
content_by_lua_block {
ngx.log(ngx.WARN, "probe SNI: ", ngx.var.ssl_server_name)
ngx.say("ok")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
}

if (!$block->request) {
$block->set_value("request", "GET /t");
}
});

run_tests();

__DATA__

=== TEST 1: domain node with the default pass_host is probed with the node domain as Host
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local original_parse_domain = core.resolver.parse_domain
core.resolver.parse_domain = function(domain)
if domain == "test.com" then
return "127.0.0.1", nil
end
return original_parse_domain(domain)
end

local t = require("lib.test_admin").test
local code = t('/apisix/admin/upstreams/1',
ngx.HTTP_PUT,
[[{
"type": "roundrobin",
"nodes": { "test.com:1988": 1 },
"checks": {
"active": {
"http_path": "/healthz",
"healthy": { "interval": 1, "successes": 1 },
"unhealthy": { "interval": 1, "http_failures": 1 }
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end

code = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{ "uri": "/hello", "upstream_id": "1" }]]
)
if code >= 300 then
ngx.status = code
return
end

local http = require "resty.http"
local httpc = http.new()
httpc:request_uri("http://127.0.0.1:" .. ngx.var.server_port .. "/hello",
{method = "GET", keepalive = false})

ngx.sleep(2)

core.resolver.parse_domain = original_parse_domain
ngx.say("done")
}
}
--- response_body
done
--- error_log
probe Host: test.com
--- no_error_log
probe Host: 127.0.0.1
--- timeout: 5



=== TEST 2: over HTTPS, the domain node is probed with the node domain as SNI (not the resolved ip)
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local original_parse_domain = core.resolver.parse_domain
core.resolver.parse_domain = function(domain)
if domain == "test.com" then
return "127.0.0.1", nil
end
return original_parse_domain(domain)
end

local t = require("lib.test_admin").test
local code = t('/apisix/admin/upstreams/1',
ngx.HTTP_PUT,
[[{
"type": "roundrobin",
"scheme": "https",
"nodes": { "test.com:1989": 1 },
"checks": {
"active": {
"type": "https",
"http_path": "/healthz",
"https_verify_certificate": false,
"healthy": { "interval": 1, "successes": 1 },
"unhealthy": { "interval": 1, "http_failures": 1 }
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end

code = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{ "uri": "/hello", "upstream_id": "1" }]]
)
if code >= 300 then
ngx.status = code
return
end

local http = require "resty.http"
local httpc = http.new()
httpc:request_uri("http://127.0.0.1:" .. ngx.var.server_port .. "/hello",
{method = "GET", keepalive = false})

ngx.sleep(2)

core.resolver.parse_domain = original_parse_domain
ngx.say("done")
}
}
--- response_body
done
--- error_log
probe SNI: test.com
--- no_error_log
probe SNI: 127.0.0.1
--- timeout: 5
Loading