This is an automated email from the ASF dual-hosted git repository.
nic-6443 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 2e43b6c605 feat(consul): support https consul server addresses (#14018)
2e43b6c605 is described below
commit 2e43b6c605566d383378a45ef1f06f3debf48a42
Author: Nic <[email protected]>
AuthorDate: Wed Oct 14 14:24:01 2026 +0800
feat(consul): support https consul server addresses (#14018)
---
apisix/discovery/consul/client.lua | 32 ++++-
apisix/discovery/consul/schema.lua | 1 +
conf/config.yaml.example | 3 +-
docs/en/latest/discovery/consul.md | 15 +++
t/discovery/consul-tls.t | 244 +++++++++++++++++++++++++++++++++++++
5 files changed, 288 insertions(+), 7 deletions(-)
diff --git a/apisix/discovery/consul/client.lua
b/apisix/discovery/consul/client.lua
index a86f6356c3..3146dd7f91 100644
--- a/apisix/discovery/consul/client.lua
+++ b/apisix/discovery/consul/client.lua
@@ -116,6 +116,16 @@ end
-- ─── resty.consul options ─────────────────────────────────────────────
+local function set_tls_opts(opts, consul_server)
+ if consul_server.ssl then
+ opts.ssl = true
+ opts.ssl_verify = consul_server.ssl_verify
+ opts.sni_host = consul_server.host
+ end
+ return opts
+end
+
+
local function get_opts(consul_server, is_catalog)
local opts = {
host = consul_server.host,
@@ -126,6 +136,7 @@ local function get_opts(consul_server, is_catalog)
token = consul_server.token,
}
}
+ set_tls_opts(opts, consul_server)
if not consul_server.keepalive then
return opts
end
@@ -251,15 +262,24 @@ function _M.format_consul_params(consul_conf)
local consul_server_list = core.table.new(0, #servers)
for _, v in pairs(servers) do
- local scheme, host, port, path = unpack(http.parse_uri(nil, v))
- if scheme ~= "http" then
- return nil, "only support consul http schema address, eg:
http://address:port"
+ local parsed, err = http.parse_uri(nil, v)
+ if not parsed then
+ return nil, "invalid consul server address: " .. err
+ .. ", the valid format: http://address:port or
https://address:port"
+ end
+ local scheme, host, port, path = unpack(parsed)
+ if scheme ~= "http" and scheme ~= "https" then
+ return nil, "only support consul http or https schema address, "
+ .. "eg: http://address:port or https://address:port"
elseif path ~= "/" or core.string.has_suffix(v, '/') then
- return nil, "invalid consul server address, the valid format:
http://address:port"
+ return nil, "invalid consul server address, the valid format: "
+ .. "http://address:port or https://address:port"
end
core.table.insert(consul_server_list, {
host = host,
port = port,
+ ssl = scheme == "https",
+ ssl_verify = consul_conf.ssl_verify ~= false,
token = consul_conf.token,
connect_timeout = consul_conf.timeout.connect,
read_timeout = consul_conf.timeout.read,
@@ -302,7 +322,7 @@ function _M.fetch_services_from_server(consul_server,
options)
local preserve_metadata = options.preserve_metadata or false
local key_builder = options.key_builder
- local consul_client = resty_consul:new({
+ local consul_client = resty_consul:new(set_tls_opts({
host = consul_server.host,
port = consul_server.port,
connect_timeout = consul_server.connect_timeout,
@@ -310,7 +330,7 @@ function _M.fetch_services_from_server(consul_server,
options)
default_args = {
token = consul_server.token
}
- })
+ }, consul_server))
-- fetch catalog
local catalog_success, catalog_res, catalog_err = pcall(function()
diff --git a/apisix/discovery/consul/schema.lua
b/apisix/discovery/consul/schema.lua
index 57080ee7b7..0462974023 100644
--- a/apisix/discovery/consul/schema.lua
+++ b/apisix/discovery/consul/schema.lua
@@ -30,6 +30,7 @@ return {
default = "64m",
},
token = {type = "string", default = ""},
+ ssl_verify = {type = "boolean", default = true},
fetch_interval = {type = "integer", minimum = 1, default = 3},
keepalive = {
type = "boolean",
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index ded74f6ceb..d07a8d0563 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -439,9 +439,10 @@ nginx_config: # Config for render the
template to generate n
# path: "logs/consul_kv.dump" # Location of the dump file.
# expire: 2592000 # Specify the expiration time of the dump
file in units of seconds.
# consul: # Consul
-# servers: # Consul address(es)
+# servers: # Consul address(es), http or https
# - "http://127.0.0.1:8500"
# - "http://127.0.0.1:8600"
+# ssl_verify: true # Default true. Verify the certificate of
https servers.
# skip_services: # Skip services during service discovery.
# - "service_a"
# timeout:
diff --git a/docs/en/latest/discovery/consul.md
b/docs/en/latest/discovery/consul.md
index fd6758bc9a..a00c439020 100644
--- a/docs/en/latest/discovery/consul.md
+++ b/docs/en/latest/discovery/consul.md
@@ -38,6 +38,7 @@ discovery:
- "http://127.0.0.1:8500" # `http://127.0.0.1:8500` and
`http://127.0.0.1:8600` are different clusters
- "http://127.0.0.1:8600" # `consul` service is default skip service
token: "..." # if your consul cluster has enabled acl
access control, you need to specify the token
+ ssl_verify: true # default true, verify the certificate of
https consul servers
skip_services: # if you need to skip special services
- "service_a"
timeout:
@@ -69,6 +70,20 @@ discovery:
- "http://127.0.0.1:8500"
```
+Each server address uses the `http` or `https` scheme, and the two can be
mixed in one list. An `https` address without a port connects to port 443. By
default APISIX verifies the certificate of an `https` Consul server against the
host in the address, so configure the CA bundle that signed it with
`apisix.ssl.ssl_trusted_certificate`:
+
+```yaml
+apisix:
+ ssl:
+ ssl_trusted_certificate: /path/to/consul-ca.crt
+discovery:
+ consul:
+ servers:
+ - "https://consul.example.com:8501"
+```
+
+Set `ssl_verify` to `false` to skip the certificate verification of `https`
servers, for example while testing against a self-signed certificate. It has no
effect on `http` servers.
+
The `keepalive` has two optional values:
- `true`, default and recommend value, use the long pull way to query consul
servers
diff --git a/t/discovery/consul-tls.t b/t/discovery/consul-tls.t
new file mode 100644
index 0000000000..3dd9308094
--- /dev/null
+++ b/t/discovery/consul-tls.t
@@ -0,0 +1,244 @@
+#
+# 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) = @_;
+
+ # a TLS endpoint fronting the consul agent on 8500, its certificate
+ # (localhost) is signed by apisix.crt; the trust store is the http level
+ # lua_ssl_trusted_certificate, as apisix.ssl.ssl_trusted_certificate
renders it
+ my $http_config = $block->http_config // <<_EOC_;
+ lua_ssl_trusted_certificate ../../certs/apisix.crt;
+
+ server {
+ listen 18501 ssl;
+ ssl_certificate ../../certs/localhost_slapd_cert.pem;
+ ssl_certificate_key ../../certs/localhost_slapd_key.pem;
+
+ location / {
+ proxy_pass http://127.0.0.1:8500;
+ proxy_read_timeout 70s;
+ }
+ }
+
+ server {
+ listen 30511;
+
+ location /hello {
+ content_by_lua_block {
+ ngx.say("server 1")
+ }
+ }
+ }
+_EOC_
+
+ $block->set_value("http_config", $http_config);
+});
+
+
+sub main::yaml_config {
+ my ($servers, $keepalive, $ssl_verify) = @_;
+ my $ssl_verify_conf = defined $ssl_verify ? " ssl_verify:
$ssl_verify\n" : "";
+ return <<_EOC_;
+apisix:
+ node_listen: 1984
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+discovery:
+ consul:
+ servers:
+ - "$servers"
+ timeout:
+ connect: 1000
+ read: 1000
+ wait: 60
+ weight: 1
+ fetch_interval: 1
+ keepalive: $keepalive
+$ssl_verify_conf
+_EOC_
+}
+
+our $apisix_yaml = <<_EOC_;
+routes:
+ -
+ uri: /hello
+ upstream:
+ service_name: service_tls
+ discovery_type: consul
+ type: roundrobin
+#END
+_EOC_
+
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: prepare consul catalog register nodes
+--- config
+location /v1/agent {
+ proxy_pass http://127.0.0.1:8500;
+}
+--- request eval
+"PUT /v1/agent/service/register\n" .
"{\"ID\":\"service_tls1\",\"Name\":\"service_tls\",\"Address\":\"127.0.0.1\",\"Port\":30511}"
+--- error_code: 200
+
+
+
+=== TEST 2: https consul server (long connect type)
+--- yaml_config eval: main::yaml_config("https://localhost:18501", "true")
+--- apisix_yaml eval: $::apisix_yaml
+--- request
+GET /hello
+--- response_body
+server 1
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: https consul server (short connect type)
+--- yaml_config eval: main::yaml_config("https://localhost:18501", "false")
+--- apisix_yaml eval: $::apisix_yaml
+--- request
+GET /hello
+--- response_body
+server 1
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: the certificate of an https consul server is verified by default
+--- http_config
+ # same endpoint as above, but its certificate is not signed by the trusted
CA
+ lua_ssl_trusted_certificate ../../certs/mtls_ca.crt;
+
+ server {
+ listen 18501 ssl;
+ ssl_certificate ../../certs/localhost_slapd_cert.pem;
+ ssl_certificate_key ../../certs/localhost_slapd_key.pem;
+
+ location / {
+ proxy_pass http://127.0.0.1:8500;
+ }
+ }
+--- yaml_config eval: main::yaml_config("https://localhost:18501", "false")
+--- apisix_yaml eval: $::apisix_yaml
+--- request
+GET /hello
+--- error_code: 503
+--- error_log eval
+[
+ qr/connect consul: https:\/\/localhost:18501\/v1 .* with error: \d+:
unable to (get local issuer|verify the first) certificate/,
+ qr/consul service not found: default\/service_tls/,
+]
+
+
+
+=== TEST 5: ssl_verify false accepts a certificate the DP does not trust
+--- http_config
+ # same endpoint as above, but its certificate is not signed by the trusted
CA
+ lua_ssl_trusted_certificate ../../certs/mtls_ca.crt;
+
+ server {
+ listen 18501 ssl;
+ ssl_certificate ../../certs/localhost_slapd_cert.pem;
+ ssl_certificate_key ../../certs/localhost_slapd_key.pem;
+
+ location / {
+ proxy_pass http://127.0.0.1:8500;
+ }
+ }
+
+ server {
+ listen 30511;
+
+ location /hello {
+ content_by_lua_block {
+ ngx.say("server 1")
+ }
+ }
+ }
+--- yaml_config eval: main::yaml_config("https://localhost:18501", "false",
"false")
+--- apisix_yaml eval: $::apisix_yaml
+--- request
+GET /hello
+--- response_body
+server 1
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: scheme per server address, other schemes are still rejected
+--- config
+ location /t {
+ content_by_lua_block {
+ local client = require("apisix.discovery.consul.client")
+ local timeout = {connect = 1000, read = 1000, wait = 60}
+
+ local servers, err = client.format_consul_params({
+ servers = {"http://127.0.0.1:8500", "https://consul.local"},
+ timeout = timeout,
+ })
+ for _, s in ipairs(servers or {}) do
+ ngx.say(s.host, ":", s.port, " ssl: ", s.ssl, " ssl_verify: ",
s.ssl_verify)
+ end
+
+ servers, err = client.format_consul_params({
+ servers = {"https://consul.local:8501"},
+ timeout = timeout,
+ ssl_verify = false,
+ })
+ ngx.say(servers[1].host, ":", servers[1].port, " ssl_verify: ",
servers[1].ssl_verify)
+
+ servers, err = client.format_consul_params({
+ servers = {"https://127.0.0.1:8501", "tcp://127.0.0.1:8500"},
+ timeout = timeout,
+ })
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+127.0.0.1:8500 ssl: false ssl_verify: true
+consul.local:443 ssl: true ssl_verify: true
+consul.local:8501 ssl_verify: false
+invalid consul server address: bad uri: tcp://127.0.0.1:8500, the valid
format: http://address:port or https://address:port
+
+
+
+=== TEST 7: clean nodes
+--- config
+location /v1/agent {
+ proxy_pass http://127.0.0.1:8500;
+}
+--- request
+PUT /v1/agent/service/deregister/service_tls1
+--- error_code: 200