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: support client certificate verification #4034

Merged
merged 3 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions apisix/admin/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ local function check_conf(id, conf, need_id)
end
end

if conf.client then
if not apisix_ssl.support_client_verification() then
spacewander marked this conversation as resolved.
Show resolved Hide resolved
return nil, {error_msg = "client tls verify unsupported"}
end

local ok, err = apisix_ssl.validate(conf.client.ca, nil)
if not ok then
return nil, {error_msg = "failed to validate client_cert: " .. err}
end
end

return need_id and id or true
end

Expand Down
13 changes: 13 additions & 0 deletions apisix/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,19 @@ end

function _M.http_access_phase()
local ngx_ctx = ngx.ctx

if ngx_ctx.api_ctx and ngx_ctx.api_ctx.ssl_client_verified then
local res = ngx_var.ssl_client_verify
if res ~= "SUCCESS" then
if res == "NONE" then
core.log.error("client certificate was not present")
else
core.log.error("clent certificate verification is not passed: ", res)
end
return core.response.exit(400)
end
end

-- always fetch table from the table pool, we don't need a reused api_ctx
local api_ctx = core.tablepool.fetch("api_ctx", 0, 32)
ngx_ctx.api_ctx = api_ctx
Expand Down
12 changes: 12 additions & 0 deletions apisix/schema_def.lua
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,18 @@ _M.ssl = {
type = "array",
items = private_key_schema,
},
client = {
type = "object",
properties = {
ca = certificate_scheme,
tokers marked this conversation as resolved.
Show resolved Hide resolved
depth = {
type = "integer",
minimum = 0,
default = 1,
},
},
required = {"ca"},
},
exptime = {
type = "integer",
minimum = 1588262400, -- 2020/5/1 0:0:0
Expand Down
10 changes: 10 additions & 0 deletions apisix/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ function _M.validate(cert, key)
return nil, "failed to parse cert: " .. err
end

if key == nil then
-- sometimes we only need to validate the cert
return true
end

key = aes_decrypt_pkey(key)
if not key then
return nil, "failed to decrypt previous encrypted key"
Expand Down Expand Up @@ -152,4 +157,9 @@ function _M.fetch_pkey(sni, pkey)
end


function _M.support_client_verification()
return ngx_ssl.verify_client ~= nil
end


return _M
21 changes: 20 additions & 1 deletion apisix/ssl/router/radixtree_sni.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ local core = require("apisix.core")
local apisix_ssl = require("apisix.ssl")
local ngx_ssl = require("ngx.ssl")
local config_util = require("apisix.core.config_util")
local ipairs = ipairs
local ipairs = ipairs
local type = type
local error = error
local str_find = core.string.find
Expand All @@ -29,6 +29,7 @@ local ssl_certificates
local radixtree_router
local radixtree_router_ver


local _M = {
version = 0.1,
server_name = ngx_ssl.server_name,
Expand Down Expand Up @@ -194,6 +195,24 @@ function _M.match_and_set(api_ctx)
end
end

if matched_ssl.value.client then
local client_cert = matched_ssl.value.client.ca
Copy link
Contributor

Choose a reason for hiding this comment

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

The name client_cert is confusing, actually, it's the CA cert(s) to verify the client cert. What about ca_cert.

local depth = matched_ssl.value.client.depth
if apisix_ssl.support_client_verification() then
local parsed_cert, err = apisix_ssl.fetch_cert(sni, client_cert)
if not parsed_cert then
return false, "failed to parse client cert: " .. err
end

local ok, err = ngx_ssl.verify_client(parsed_cert, depth)
if not ok then
return false, err
end

api_ctx.ssl_client_verified = true
end
end

return true
end

Expand Down
2 changes: 2 additions & 0 deletions docs/en/latest/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,8 @@ Return response from etcd currently.
| key | True | Private key | https private key | |
| certs | False | An array of certificate | when you need to configure multiple certificate for the same domain, you can pass extra https certificates (excluding the one given as cert) in this field | |
| keys | False | An array of private key | https private keys. The keys should be paired with certs above | |
| client.ca | False | Certificate| set the CA certificate which will use to verify client. This feature requires OpenResty 1.19+. | |
| client.depth | False | Certificate| set the verification depth in the client certificates chain, default to 1. This feature requires OpenResty 1.19+. | |
Comment on lines +787 to +788
Copy link
Contributor

Choose a reason for hiding this comment

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

The first letter should be capitalized. set --> Set

set the CA certificate which will use to verify client.

Copy link
Member Author

Choose a reason for hiding this comment

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

None of the description in this table start with uppercase letter.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it.

| snis | True | Match Rules | a non-empty arrays of https SNI | |
| labels | False | Match Rules | Key/value pairs to specify attributes | {"version":"v2","build":"16","env":"production"} |
| create_time | False | Auxiliary | epoch timestamp in second, will be created automatically if missing | 1602883670 |
Expand Down
2 changes: 2 additions & 0 deletions docs/zh/latest/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,8 @@ $ curl http://127.0.0.1:9080/get
| key | 必需 | 私钥 | https 证书私钥 | |
| certs | 可选 | 证书字符串数组 | 当你想给同一个域名配置多个证书时,除了第一个证书需要通过 cert 传递外,剩下的证书可以通过该参数传递上来 | |
| keys | 可选 | 私钥字符串数组 | certs 对应的证书私钥,注意要跟 certs 一一对应 | |
| client.ca | 可选 | 证书| 设置将用于客户端证书校验的 CA 证书。该特性需要 OpenResty 1.19+ | |
| client.depth | 可选 | 辅助| 设置客户端证书校验的深度,默认为 1。该特性需要 OpenResty 1.19+ | |
| snis | 必需 | 匹配规则 | 非空数组形式,可以匹配多个 SNI | |
| labels | 可选 | 匹配规则 | 标识附加属性的键值对 | {"version":"v2","build":"16","env":"production"} |
| create_time | 可选 | 辅助 | 单位为秒的 epoch 时间戳,如果不指定则自动创建 | 1602883670 |
Expand Down
Loading