diff --git a/apisix/http/router/radixtree_sni.lua b/apisix/http/router/radixtree_sni.lua index 1eb7aa54508b..d042aeedc957 100644 --- a/apisix/http/router/radixtree_sni.lua +++ b/apisix/http/router/radixtree_sni.lua @@ -25,6 +25,7 @@ local error = error local str_find = string.find local aes = require "resty.aes" local assert = assert +local str_gsub = string.gsub local ngx_decode_base64 = ngx.decode_base64 local ssl_certificates local radixtree_router @@ -80,7 +81,6 @@ local function create_router(ssl_items) end end - local idx = idx + 1 route_items[idx] = { paths = sni, @@ -96,6 +96,10 @@ local function create_router(ssl_items) end core.log.info("route items: ", core.json.delay_encode(route_items, true)) + -- for testing + if #route_items > 1 then + core.log.info("we have more than 1 ssl certs now") + end local router, err = radixtree_new(route_items) if not router then return nil, err @@ -151,7 +155,7 @@ function _M.match_and_set(api_ctx) local sni sni, err = ngx_ssl.server_name() if type(sni) ~= "string" then - return false, "failed to fetch SNI: " .. (err or "not found") + return false, "failed to fetch SSL certificate: " .. (err or "not found") end core.log.debug("sni: ", sni) @@ -159,7 +163,7 @@ function _M.match_and_set(api_ctx) local sni_rev = sni:reverse() local ok = radixtree_router:dispatch(sni_rev, nil, api_ctx) if not ok then - core.log.warn("not found any valid sni configuration") + core.log.warn("failed to find any SSL certificate by SNI: ", sni) return false end @@ -172,14 +176,19 @@ function _M.match_and_set(api_ctx) end end if not matched then - core.log.warn("not found any valid sni configuration, matched sni: ", - core.json.delay_encode(api_ctx.matched_sni, true), " current sni: ", sni) + local log_snis = core.json.encode(api_ctx.matched_sni, true) + if log_snis ~= nil then + log_snis = str_gsub(log_snis:reverse(), "%[", "%]") + log_snis = str_gsub(log_snis, "%]", "%[", 1) + end + core.log.warn("failed to find any SSL certificate by SNI: ", + sni, " matched SNIs: ", log_snis) return false end else if str_find(sni_rev, ".", #api_ctx.matched_sni, true) then - core.log.warn("not found any valid sni configuration, matched sni: ", - api_ctx.matched_sni:reverse(), " current sni: ", sni) + core.log.warn("failed to find any SSL certificate by SNI: ", + sni, " matched SNI: ", api_ctx.matched_sni:reverse()) return false end end diff --git a/t/node/invalid-service.t b/t/node/invalid-service.t index 4a6af138e6d0..0b7423284cea 100644 --- a/t/node/invalid-service.t +++ b/t/node/invalid-service.t @@ -39,7 +39,7 @@ __DATA__ end ngx.print(core.json.encode(res.body)) - ngx.sleep(0.5) + ngx.sleep(1) } } --- request diff --git a/t/node/invalid-upstream.t b/t/node/invalid-upstream.t index b6138ab617c6..1d83e9eaa6b8 100644 --- a/t/node/invalid-upstream.t +++ b/t/node/invalid-upstream.t @@ -38,7 +38,7 @@ __DATA__ end ngx.print(core.json.encode(res.body)) - ngx.sleep(0.5) + ngx.sleep(1) } } --- request diff --git a/t/router/multi-ssl-certs.t b/t/router/multi-ssl-certs.t new file mode 100644 index 000000000000..61fe22af2c71 --- /dev/null +++ b/t/router/multi-ssl-certs.t @@ -0,0 +1,360 @@ +# +# 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'; + +log_level('debug'); +no_root_location(); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests; + +__DATA__ + +=== TEST 1: set ssl(sni: www.test.com) +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("conf/cert/apisix.crt") + local ssl_key = t.read_file("conf/cert/apisix.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "www.test.com"} + + local code, body = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "node": { + "value": { + "sni": "www.test.com" + }, + "key": "/apisix/ssl/1" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } +} +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 2: set route(id: 1) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 3: client request +--- config +listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + +location /t { + content_by_lua_block { + -- etcd sync + ngx.sleep(0.2) + + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "www.test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /hello HTTP/1.0\r\nHost: www.test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } +} +--- request +GET /t +--- response_body eval +qr{connected: 1 +ssl handshake: userdata +sent http request: 62 bytes. +received: HTTP/1.1 200 OK +received: Content-Type: text/plain +received: Connection: close +received: Server: \w+ +received: \nreceived: hello world +close: 1 nil} +--- error_log +lua ssl server name: "www.test.com" +--- no_error_log +[error] +[alert] + + + +=== TEST 4: set second ssl(sni: *.test2.com) +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("conf/cert/test2.crt") + local ssl_key = t.read_file("conf/cert/test2.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "*.test2.com"} + + local code, body = t.test('/apisix/admin/ssl/2', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "node": { + "value": { + "sni": "*.test2.com" + }, + "key": "/apisix/ssl/2" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } +} +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 5: client request: www.test2.com +--- config +listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + +location /t { + content_by_lua_block { + -- etcd sync + ngx.sleep(0.2) + + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "www.test2.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } +} +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: 18: self signed certificate +--- error_log +lua ssl server name: "www.test2.com" +we have more than 1 ssl certs now +--- no_error_log +[error] +[alert] + + + +=== TEST 6: set third ssl(sni: apisix.dev) +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("conf/cert/apisix_admin_ssl.crt") + local ssl_key = t.read_file("conf/cert/apisix_admin_ssl.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "apisix.dev"} + + local code, body = t.test('/apisix/admin/ssl/3', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "node": { + "value": { + "sni": "apisix.dev" + }, + "key": "/apisix/ssl/3" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } +} +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 7: client request: apisix.dev +--- config +listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + +location /t { + content_by_lua_block { + -- etcd sync + ngx.sleep(0.2) + + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "apisix.dev", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } +} +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: 18: self signed certificate +--- error_log +lua ssl server name: "apisix.dev" +we have more than 1 ssl certs now +--- no_error_log +[error] +[alert] + + + +=== TEST 19: remove test ssl certs +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + t.test('/apisix/admin/ssl/1', ngx.HTTP_DELETE) + t.test('/apisix/admin/ssl/2', ngx.HTTP_DELETE) + t.test('/apisix/admin/ssl/3', ngx.HTTP_DELETE) + + } +} +--- request +GET /t +--- no_error_log +[error] diff --git a/t/router/radixtree-sni.t b/t/router/radixtree-sni.t index 86724e04f2ce..06388a76c435 100644 --- a/t/router/radixtree-sni.t +++ b/t/router/radixtree-sni.t @@ -207,7 +207,7 @@ GET /t connected: 1 failed to do SSL handshake: certificate host mismatch --- error_log -not found any valid sni configuration +failed to find any SSL certificate by SNI @@ -561,7 +561,7 @@ connected: 1 failed to do SSL handshake: certificate host mismatch --- error_log lua ssl server name: "aa.bb.test2.com" -not found any valid sni configuration, matched sni: *.test2.com current sni: aa.bb.test2.com +failed to find any SSL certificate by SNI: aa.bb.test2.com matched SNI: *.test2.com --- no_error_log [error] [alert] @@ -856,7 +856,7 @@ connected: 1 failed to do SSL handshake: certificate host mismatch --- error_log lua ssl server name: "aa.bb.test2.com" -not found any valid sni configuration, matched sni: ["moc.2tset","moc.2tset.*"] current sni: aa.bb.test2.com +failed to find any SSL certificate by SNI: aa.bb.test2.com matched SNIs: ["*.test2.com","test2.com"] --- no_error_log [error] [alert] @@ -904,7 +904,6 @@ passed === TEST 20: client request: test2.com --- config listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; - location /t { content_by_lua_block { -- etcd sync