This is an automated email from the ASF dual-hosted git repository.
baoyuan 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 9b1e54627 fix(security): TLS ssl_verify hardcoding and credential
encryption issues (#13203)
9b1e54627 is described below
commit 9b1e54627b614de251cfc2b35aedbd9f4abeaed2
Author: AlinsRan <[email protected]>
AuthorDate: Fri Apr 17 11:56:40 2026 +0800
fix(security): TLS ssl_verify hardcoding and credential encryption issues
(#13203)
---
apisix/core/utils.lua | 9 +-
apisix/plugins/ai-rag.lua | 16 ++-
apisix/plugins/ai-rag/embeddings/azure_openai.lua | 5 +-
.../ai-rag/vector-search/azure_ai_search.lua | 5 +-
apisix/plugins/aws-lambda.lua | 12 +-
apisix/plugins/wolf-rbac.lua | 77 ++++++-----
t/plugin/ai-rag.t | 144 +++++++++++++++++++++
t/plugin/aws-lambda.t | 97 ++++++++++++++
t/plugin/wolf-rbac.t | 127 ++++++++++++++++++
9 files changed, 449 insertions(+), 43 deletions(-)
diff --git a/apisix/core/utils.lua b/apisix/core/utils.lua
index 0765465d0..469af448c 100644
--- a/apisix/core/utils.lua
+++ b/apisix/core/utils.lua
@@ -421,6 +421,9 @@ local function get_root_conf(str, conf, field)
local num_of_matches = #matches
for i = 1, num_of_matches - 1 , 1 do
conf = conf[matches[i]]
+ if type(conf) ~= "table" then
+ return nil, nil
+ end
end
-- return the table and the last field
@@ -441,12 +444,12 @@ function _M.check_https(fields, conf, plugin_name)
local new_conf, new_field = get_root_conf(field, conf)
if not new_conf then
- return
+ goto continue
end
local value = new_conf[new_field]
if not value then
- return
+ goto continue
end
if type(value) == "table" then
@@ -456,6 +459,8 @@ function _M.check_https(fields, conf, plugin_name)
else
find_and_log(field, plugin_name, value)
end
+
+ ::continue::
end
end
diff --git a/apisix/plugins/ai-rag.lua b/apisix/plugins/ai-rag.lua
index fa40f5605..88f4e99c2 100644
--- a/apisix/plugins/ai-rag.lua
+++ b/apisix/plugins/ai-rag.lua
@@ -31,7 +31,10 @@ local HTTP_BAD_REQUEST = ngx.HTTP_BAD_REQUEST
local schema = {
type = "object",
properties = {
- type = "object",
+ ssl_verify = {
+ type = "boolean",
+ default = true,
+ },
embeddings_provider = {
type = "object",
properties = {
@@ -83,12 +86,18 @@ local _M = {
function _M.check_schema(conf)
+ core.utils.check_tls_bool({"ssl_verify"}, conf, _M.name)
+ core.utils.check_https({
+ "embeddings_provider.azure_openai.endpoint",
+ "vector_search_provider.azure_ai_search.endpoint",
+ }, conf, _M.name)
return core.schema.check(schema, conf)
end
function _M.access(conf, ctx)
local httpc = http.new()
+ local ssl_verify = conf.ssl_verify ~= false
local body_tab, err = core.request.get_json_request_body_table()
if not body_tab then
return HTTP_BAD_REQUEST, err
@@ -120,7 +129,8 @@ function _M.access(conf, ctx)
end
local embeddings, status, err =
embeddings_driver.get_embeddings(embeddings_provider_conf,
-
body_tab["ai_rag"].embeddings, httpc)
+
body_tab["ai_rag"].embeddings, httpc,
+ ssl_verify)
if not embeddings then
core.log.error("could not get embeddings: ", err)
return status, err
@@ -129,7 +139,7 @@ function _M.access(conf, ctx)
local search_body = body_tab["ai_rag"].vector_search
search_body.embeddings = embeddings
local res, status, err =
vector_search_driver.search(vector_search_provider_conf,
- search_body, httpc)
+ search_body, httpc,
ssl_verify)
if not res then
core.log.error("could not get vector_search result: ", err)
return status, err
diff --git a/apisix/plugins/ai-rag/embeddings/azure_openai.lua
b/apisix/plugins/ai-rag/embeddings/azure_openai.lua
index b6bacbf32..78dc14974 100644
--- a/apisix/plugins/ai-rag/embeddings/azure_openai.lua
+++ b/apisix/plugins/ai-rag/embeddings/azure_openai.lua
@@ -34,7 +34,7 @@ _M.schema = {
required = { "endpoint", "api_key" }
}
-function _M.get_embeddings(conf, body, httpc)
+function _M.get_embeddings(conf, body, httpc, ssl_verify)
local body_tab, err = core.json.encode(body)
if not body_tab then
return nil, HTTP_INTERNAL_SERVER_ERROR, err
@@ -46,7 +46,8 @@ function _M.get_embeddings(conf, body, httpc)
["Content-Type"] = "application/json",
["api-key"] = conf.api_key,
},
- body = body_tab
+ body = body_tab,
+ ssl_verify = ssl_verify,
})
if not res or not res.body then
diff --git a/apisix/plugins/ai-rag/vector-search/azure_ai_search.lua
b/apisix/plugins/ai-rag/vector-search/azure_ai_search.lua
index 7a0106490..3f81981fa 100644
--- a/apisix/plugins/ai-rag/vector-search/azure_ai_search.lua
+++ b/apisix/plugins/ai-rag/vector-search/azure_ai_search.lua
@@ -34,7 +34,7 @@ _M.schema = {
}
-function _M.search(conf, search_body, httpc)
+function _M.search(conf, search_body, httpc, ssl_verify)
local body = {
vectorQueries = {
{
@@ -55,7 +55,8 @@ function _M.search(conf, search_body, httpc)
["Content-Type"] = "application/json",
["api-key"] = conf.api_key,
},
- body = final_body
+ body = final_body,
+ ssl_verify = ssl_verify,
})
if not res or not res.body then
diff --git a/apisix/plugins/aws-lambda.lua b/apisix/plugins/aws-lambda.lua
index 1b172af41..9897ffbdb 100644
--- a/apisix/plugins/aws-lambda.lua
+++ b/apisix/plugins/aws-lambda.lua
@@ -184,4 +184,14 @@ end
local serverless_obj = require("apisix.plugins.serverless.generic-upstream")
-return serverless_obj(plugin_name, plugin_version, priority,
request_processor, aws_authz_schema)
+local plugin = serverless_obj(plugin_name, plugin_version, priority,
request_processor,
+ aws_authz_schema)
+
+-- encrypt sensitive credential fields at rest
+plugin.schema.encrypt_fields = {
+ "authorization.apikey",
+ "authorization.iam.accesskey",
+ "authorization.iam.secretkey",
+}
+
+return plugin
diff --git a/apisix/plugins/wolf-rbac.lua b/apisix/plugins/wolf-rbac.lua
index 716fd2630..9d54d3758 100644
--- a/apisix/plugins/wolf-rbac.lua
+++ b/apisix/plugins/wolf-rbac.lua
@@ -48,6 +48,9 @@ local schema = {
type = "string",
default = "X-"
},
+ ssl_verify = {
+ type = "boolean",
+ },
}
}
@@ -108,7 +111,7 @@ local function new_headers()
end
-- timeout in ms
-local function http_req(method, uri, body, myheaders, timeout)
+local function http_req(method, uri, body, myheaders, timeout, ssl_verify)
if not myheaders then
myheaders = new_headers()
end
@@ -122,7 +125,7 @@ local function http_req(method, uri, body, myheaders,
timeout)
method = method,
headers = myheaders,
body = body,
- ssl_verify = false
+ ssl_verify = ssl_verify == true,
})
if not res then
@@ -134,21 +137,23 @@ local function http_req(method, uri, body, myheaders,
timeout)
return res
end
-local function http_get(uri, myheaders, timeout)
- return http_req("GET", uri, nil, myheaders, timeout)
+local function http_get(uri, myheaders, timeout, ssl_verify)
+ return http_req("GET", uri, nil, myheaders, timeout, ssl_verify)
end
function _M.check_schema(conf)
- local check = {"server"}
- core.utils.check_https(check, conf, plugin_name)
- core.log.info("input conf server: ", conf.server)
-
local ok, err = core.schema.check(schema, conf)
if not ok then
return false, err
end
+ local check = {"server"}
+ core.utils.check_https(check, conf, plugin_name)
+ core.log.info("input conf server: ", conf.server)
+
+ core.utils.check_tls_bool({"ssl_verify"}, conf, plugin_name)
+
return true
end
@@ -170,7 +175,8 @@ local function fetch_rbac_token(ctx)
end
-local function check_url_permission(server, appid, action, resName, client_ip,
wolf_token)
+local function check_url_permission(server, appid, action, resName,
+ client_ip, wolf_token, ssl_verify)
local retry_max = 3
local errmsg
local userInfo
@@ -186,7 +192,7 @@ local function check_url_permission(server, appid, action,
resName, client_ip, w
for i = 1, retry_max do
-- TODO: read apisix info.
- res, err = http_get(url, headers, timeout)
+ res, err = http_get(url, headers, timeout, ssl_verify)
if err then
break
else
@@ -271,9 +277,10 @@ function _M.rewrite(conf, ctx)
end
core.log.info("consumer appid: ", appid)
local server = cur_consumer.auth_conf.server
+ local ssl_verify = cur_consumer.auth_conf.ssl_verify
local res = check_url_permission(server, appid, action, url,
- client_ip, wolf_token)
+ client_ip, wolf_token, ssl_verify)
core.log.info(" check_url_permission(appid: ", appid,
", action: ", action, ", url: ", url,
") res status: ", res.status, ", err: ", res.err)
@@ -341,43 +348,47 @@ local function get_consumer(appid)
return consumer
end
-local function request_to_wolf_server(method, uri, headers, body)
+local function request_to_wolf_server(method, uri, headers, body, ssl_verify)
headers["Content-Type"] = "application/json; charset=utf-8"
local timeout = 1000 * 5
- core.log.info("request to wolf-server [method: ", method,
- ", uri: ", uri, ", timeout: ", timeout, "] ....")
- local res, err = http_req(method, uri, core.json.encode(body), headers,
timeout)
- if not res then
- core.log.error("request to wolf-server [method: ", method,
- ", uri: ", uri, "] failed! err: ", err)
+ local request_debug = core.json.delay_encode(
+ {method = method, uri = uri, timeout = timeout}
+ )
+
+ core.log.info("request [", request_debug, "] ....")
+ local encoded_body, err = core.json.encode(body)
+ if not encoded_body then
+ core.log.error("request [", request_debug, "] failed! err: ", err)
return core.response.exit(500,
fail_response("request to wolf-server failed!")
)
end
- core.log.info("request to wolf-server [method: ", method,
- ", uri: ", uri, "] status: ", res.status)
- if res.status ~= 200 then
- core.log.error("request to wolf-server [method: ", method,
- ", uri: ", uri, "] failed! status: ", res.status)
+ local res, err = http_req(method, uri, encoded_body, headers, timeout,
ssl_verify)
+ if not res then
+ core.log.error("request [", request_debug, "] failed! err: ", err)
return core.response.exit(500,
- fail_response("request to wolf-server failed!")
+ fail_response("request to wolf-server failed!")
)
end
+ core.log.info("request [", request_debug, "] status: ", res.status)
+
+ if res.status ~= 200 then
+ core.log.error("request [", request_debug, "] failed! status: ",
+ res.status)
+ return core.response.exit(500, fail_response("request to wolf-server
failed!"))
+ end
local body, err = json.decode(res.body)
if not body then
- core.log.error("request to wolf-server [method: ", method,
- ", uri: ", uri, "] failed! err:", err)
+ core.log.error("request [", request_debug, "] failed! err:", err)
return core.response.exit(500, fail_response("request to wolf-server
failed!"))
end
if not body.ok then
- core.log.error("request to wolf-server [method: ", method,
- ", uri: ", uri, "] failed! reason: ", body.reason)
+ core.log.error("request [", request_debug, "] failed! reason: ",
body.reason)
return core.response.exit(200, fail_response("request to wolf-server
failed!"))
end
- core.log.info("request to wolf-server [method: ", method,
- ", uri: ", uri, "] success")
+ core.log.info("request [", request_debug, "] success!")
return body
end
@@ -396,7 +407,7 @@ local function wolf_rbac_login()
local uri = consumer.auth_conf.server .. '/wolf/rbac/login.rest'
local headers = new_headers()
- local body = request_to_wolf_server('POST', uri, headers, args)
+ local body = request_to_wolf_server('POST', uri, headers, args,
consumer.auth_conf.ssl_verify)
local userInfo = body.data.userInfo
local wolf_token = body.data.token
@@ -440,7 +451,7 @@ local function wolf_rbac_change_pwd()
local uri = consumer.auth_conf.server .. '/wolf/rbac/change_pwd'
local headers = new_headers()
headers['x-rbac-token'] = wolf_token
- request_to_wolf_server('POST', uri, headers, args)
+ request_to_wolf_server('POST', uri, headers, args,
consumer.auth_conf.ssl_verify)
core.response.exit(200, success_response('success to change password', {
}))
end
@@ -455,7 +466,7 @@ local function wolf_rbac_user_info()
local uri = consumer.auth_conf.server .. '/wolf/rbac/user_info'
local headers = new_headers()
headers['x-rbac-token'] = wolf_token
- local body = request_to_wolf_server('GET', uri, headers, {})
+ local body = request_to_wolf_server('GET', uri, headers, {},
consumer.auth_conf.ssl_verify)
local userInfo = body.data.userInfo
core.response.exit(200, success_response(nil, {user_info = userInfo}))
end
diff --git a/t/plugin/ai-rag.t b/t/plugin/ai-rag.t
index c0e1ccfc2..240ef7c8a 100644
--- a/t/plugin/ai-rag.t
+++ b/t/plugin/ai-rag.t
@@ -452,3 +452,147 @@ POST /v1/responses
--- error_code: 200
--- response_body eval
qr/"input":"which service is good for devops\\npassed"/
+
+
+
+=== TEST 15: ssl_verify defaults to true
+--- config
+ location /t {
+ content_by_lua_block {
+ local plugin = require("apisix.plugins.ai-rag")
+ local conf = {
+ embeddings_provider = {
+ azure_openai = {
+ api_key = "key",
+ endpoint = "http://a.b.com"
+ }
+ },
+ vector_search_provider = {
+ azure_ai_search = {
+ api_key = "key",
+ endpoint = "http://a.b.com"
+ }
+ }
+ }
+ local ok, err = plugin.check_schema(conf)
+ if not ok then
+ ngx.say(err)
+ return
+ end
+ ngx.say(conf.ssl_verify)
+ }
+ }
+--- response_body
+true
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: ssl_verify can be set to false
+--- config
+ location /t {
+ content_by_lua_block {
+ local plugin = require("apisix.plugins.ai-rag")
+ local conf = {
+ ssl_verify = false,
+ embeddings_provider = {
+ azure_openai = {
+ api_key = "key",
+ endpoint = "http://a.b.com"
+ }
+ },
+ vector_search_provider = {
+ azure_ai_search = {
+ api_key = "key",
+ endpoint = "http://a.b.com"
+ }
+ }
+ }
+ local ok, err = plugin.check_schema(conf)
+ if not ok then
+ ngx.say(err)
+ return
+ end
+ ngx.say(conf.ssl_verify)
+ }
+ }
+--- response_body
+false
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: ssl_verify=false is passed through to resty.http request_uri
+--- extra_init_by_lua
+ local http = require("resty.http")
+ local old_new = http.new
+ http.new = function(self)
+ local instance = old_new(self)
+ local old_request_uri = instance.request_uri
+ instance.request_uri = function(self, uri, opts)
+ if opts then
+ ngx.log(ngx.INFO, "ai_rag ssl_verify: ",
tostring(opts.ssl_verify))
+ end
+ return old_request_uri(self, uri, opts)
+ end
+ return instance
+ end
+--- 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,
+ [[{
+ "uris": ["/echo"],
+ "plugins": {
+ "ai-rag": {
+ "ssl_verify": false,
+ "embeddings_provider": {
+ "azure_openai": {
+ "endpoint":
"http://localhost:3623/embeddings",
+ "api_key": "key"
+ }
+ },
+ "vector_search_provider": {
+ "azure_ai_search": {
+ "endpoint": "http://localhost:3623/search",
+ "api_key": "key"
+ }
+ }
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {"127.0.0.1:1980": 1},
+ "scheme": "http"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ -- send a real request to trigger both embeddings and
vector-search HTTP calls
+ local http_client = require("resty.http")
+ local httpc = http_client.new()
+ local res, err = httpc:request_uri("http://127.0.0.1:" ..
ngx.var.server_port .. "/echo", {
+ method = "POST",
+ headers = {["Content-Type"] = "application/json"},
+ body =
[[{"ai_rag":{"vector_search":{"fields":"something"},"embeddings":{"input":"test"}}}]],
+ })
+ if not res then
+ ngx.say("request failed: ", err)
+ return
+ end
+ ngx.say("done")
+ }
+ }
+--- response_body
+done
+--- error_log
+ai_rag ssl_verify: false
diff --git a/t/plugin/aws-lambda.t b/t/plugin/aws-lambda.t
index e941d84c2..9c072ac12 100644
--- a/t/plugin/aws-lambda.t
+++ b/t/plugin/aws-lambda.t
@@ -275,3 +275,100 @@ qr/passed
Authz-Header - AWS4-HMAC-SHA256 [ -~]*
AMZ-Date - [\d]+T[\d]+Z
invoked/
+
+
+
+=== TEST 7: cleanup route before encryption test
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code = t('/apisix/admin/routes/1', ngx.HTTP_DELETE)
+ ngx.say(code)
+ }
+ }
+--- response_body
+200
+
+
+
+=== TEST 8: iam credentials (accesskey, secretkey) and apikey are encrypted at
rest
+--- yaml_config
+apisix:
+ data_encryption:
+ enable: true
+ keyring:
+ - edd1c9f0985e76a2
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local json = require("toolkit.json")
+
+ -- create route with both IAM credentials and apikey
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "aws-lambda": {
+ "function_uri":
"http://localhost:8765/generic",
+ "authorization": {
+ "apikey": "test-api-key",
+ "iam": {
+ "accesskey": "test-access-key",
+ "secretkey": "test-secret-key"
+ }
+ }
+ }
+ },
+ "uri": "/aws"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ -- admin API returns plaintext (framework decrypts on read)
+ local code, _, res = t('/apisix/admin/routes/1', ngx.HTTP_GET)
+ res = json.decode(res)
+
ngx.say(res.value.plugins["aws-lambda"].authorization.iam.secretkey)
+
ngx.say(res.value.plugins["aws-lambda"].authorization.iam.accesskey)
+ ngx.say(res.value.plugins["aws-lambda"].authorization.apikey)
+
+ -- etcd stores ciphertext: assert value is a non-empty string !=
plaintext
+ local etcd = require("apisix.core.etcd")
+ local etcd_res = assert(etcd.get('/routes/1'))
+ local plugin_conf = etcd_res.body.node.value.plugins["aws-lambda"]
+ local stored_secret = plugin_conf.authorization.iam.secretkey
+ local stored_access = plugin_conf.authorization.iam.accesskey
+ local stored_apikey = plugin_conf.authorization.apikey
+
+ if type(stored_secret) == "string" and #stored_secret > 0
+ and stored_secret ~= "test-secret-key" then
+ ngx.say("secretkey encrypted: ok")
+ else
+ ngx.say("secretkey encrypted: FAIL")
+ end
+ if type(stored_access) == "string" and #stored_access > 0
+ and stored_access ~= "test-access-key" then
+ ngx.say("accesskey encrypted: ok")
+ else
+ ngx.say("accesskey encrypted: FAIL")
+ end
+ if type(stored_apikey) == "string" and #stored_apikey > 0
+ and stored_apikey ~= "test-api-key" then
+ ngx.say("apikey encrypted: ok")
+ else
+ ngx.say("apikey encrypted: FAIL")
+ end
+ }
+ }
+--- response_body
+test-secret-key
+test-access-key
+test-api-key
+secretkey encrypted: ok
+accesskey encrypted: ok
+apikey encrypted: ok
diff --git a/t/plugin/wolf-rbac.t b/t/plugin/wolf-rbac.t
index 429478a02..86cf0bf45 100644
--- a/t/plugin/wolf-rbac.t
+++ b/t/plugin/wolf-rbac.t
@@ -733,3 +733,130 @@ X-Nickname: administrator
consumer merge echo plugins
--- no_error_log
[error]
+
+
+
+=== TEST 38: ssl_verify=false is passed through to HTTP client
+--- extra_init_by_lua
+ local http = require("resty.http")
+ local old_new = http.new
+ http.new = function(self)
+ local instance = old_new(self)
+ local old_request_uri = instance.request_uri
+ instance.request_uri = function(self, uri, opts)
+ if opts then
+ ngx.log(ngx.INFO, "ssl_verify: ", tostring(opts.ssl_verify))
+ end
+ return old_request_uri(self, uri, opts)
+ end
+ return instance
+ end
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "wolf_rbac_ssl_verify_false",
+ "plugins": {
+ "wolf-rbac": {
+ "appid": "wolf-rbac-app",
+ "server": "http://127.0.0.1:1982",
+ "ssl_verify": false
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t('/apisix/plugin/wolf-rbac/login',
+ ngx.HTTP_POST,
+ [[{"appid": "wolf-rbac-app", "username": "admin", "password":
"123456"}]],
+ nil,
+ {["Content-Type"] = "application/json"}
+ )
+ ngx.say(code)
+ }
+ }
+--- error_log
+ssl_verify: false
+--- no_error_log
+[error]
+
+
+
+=== TEST 39: ssl_verify=true is passed through to HTTP client
+--- extra_init_by_lua
+ local http = require("resty.http")
+ local old_new = http.new
+ http.new = function(self)
+ local instance = old_new(self)
+ local old_request_uri = instance.request_uri
+ instance.request_uri = function(self, uri, opts)
+ if opts then
+ ngx.log(ngx.INFO, "ssl_verify: ", tostring(opts.ssl_verify))
+ end
+ return old_request_uri(self, uri, opts)
+ end
+ return instance
+ end
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "wolf_rbac_ssl_verify_true",
+ "plugins": {
+ "wolf-rbac": {
+ "appid": "wolf-rbac-app",
+ "server": "http://127.0.0.1:1982",
+ "ssl_verify": true
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t('/apisix/plugin/wolf-rbac/login',
+ ngx.HTTP_POST,
+ [[{"appid": "wolf-rbac-app", "username": "admin", "password":
"123456"}]],
+ nil,
+ {["Content-Type"] = "application/json"}
+ )
+ ngx.say(code)
+ }
+ }
+--- error_log
+ssl_verify: true
+
+
+
+=== TEST 40: ssl_verify rejects non-boolean value
+--- config
+ location /t {
+ content_by_lua_block {
+ local plugin = require("apisix.plugins.wolf-rbac")
+ local conf = {ssl_verify = "true"}
+ local ok, err = plugin.check_schema(conf)
+ if not ok then
+ ngx.say(err)
+ return
+ end
+ ngx.say("schema passed unexpectedly")
+ }
+ }
+--- response_body_like eval
+qr/ssl_verify/
+--- no_error_log
+[error]