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(ssl): support get upstream cert from ssl object #7221

Merged
merged 15 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from 13 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
2 changes: 1 addition & 1 deletion apisix/admin/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ local function check_conf(id, conf, need_id)
conf.id = id

core.log.info("schema: ", core.json.delay_encode(core.schema.ssl))
core.log.info("conf : ", core.json.delay_encode(conf))
core.log.info("conf: ", core.json.delay_encode(conf))

local ok, err = apisix_ssl.check_ssl_conf(false, conf)
if not ok then
Expand Down
23 changes: 23 additions & 0 deletions apisix/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,29 @@ function _M.http_access_phase()
or route_val.upstream
end

if api_ctx.matched_upstream and api_ctx.matched_upstream.tls and
api_ctx.matched_upstream.tls.client_cert_id then

local cert_id = api_ctx.matched_upstream.tls.client_cert_id
local upstream_ssl = router.router_ssl.get_by_id(cert_id)
if not upstream_ssl or upstream_ssl.type ~= "client" then
soulbird marked this conversation as resolved.
Show resolved Hide resolved
local err = upstream_ssl and
"ssl type should be 'client'" or
"ssl id [" .. cert_id .. "] not exits"
core.log.error("failed to get ssl cert: ", err)

if is_http then
return core.response.exit(502)
end

return ngx_exit(1)
end

core.log.info("matched ssl: ",
core.json.delay_encode(upstream_ssl, true))
api_ctx.upstream_ssl = upstream_ssl
end

if enable_websocket then
api_ctx.var.upstream_upgrade = api_ctx.var.http_upgrade
api_ctx.var.upstream_connection = api_ctx.var.http_connection
Expand Down
40 changes: 34 additions & 6 deletions apisix/schema_def.lua
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ local upstream_schema = {
tls = {
type = "object",
properties = {
client_cert_id = id_schema,
client_cert = certificate_scheme,
client_key = private_key_schema,
verify = {
Expand All @@ -414,8 +415,17 @@ local upstream_schema = {
},
},
dependencies = {
client_cert = {"client_key"},
client_key = {"client_cert"},
client_cert = {
required = {"client_key"},
["not"] = {required = {"client_cert_id"}}
},
client_key = {
required = {"client_cert"},
["not"] = {required = {"client_cert_id"}}
},
client_cert_id = {
["not"] = {required = {"client_client", "client_key"}}
}
}
},
keepalive_pool = {
Expand Down Expand Up @@ -504,7 +514,7 @@ local upstream_schema = {
oneOf = {
{required = {"type", "nodes"}},
{required = {"type", "service_name", "discovery_type"}},
},
}
}

-- TODO: add more nginx variable support
Expand Down Expand Up @@ -722,6 +732,14 @@ _M.ssl = {
type = "object",
properties = {
id = id_schema,
type = {
description = "ssl certificate type, " ..
"server to server certificate, " ..
"client to client certificate for upstream",
type = "string",
default = "server",
enum = {"server", "client"}
},
cert = certificate_scheme,
key = private_key_schema,
sni = {
Expand Down Expand Up @@ -772,10 +790,20 @@ _M.ssl = {
create_time = timestamp_def,
update_time = timestamp_def
},
oneOf = {
{required = {"sni", "key", "cert"}},
{required = {"snis", "key", "cert"}}
["if"] = {
properties = {
type = {
enum = {"server"},
},
},
},
["then"] = {
oneOf = {
{required = {"sni", "key", "cert"}},
{required = {"snis", "key", "cert"}}
}
},
["else"] = {required = {"key", "cert"}}
}


Expand Down
4 changes: 4 additions & 0 deletions apisix/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ function _M.check_ssl_conf(in_dp, conf)
return nil, err
end

if conf.type == "client" then
return true
end

local numcerts = conf.certs and #conf.certs or 0
local numkeys = conf.keys and #conf.keys or 0
if numcerts ~= numkeys then
Expand Down
18 changes: 17 additions & 1 deletion apisix/ssl/router/radixtree_sni.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ local error = error
local str_find = core.string.find
local str_gsub = string.gsub
local str_lower = string.lower
local tostring = tostring
local ssl_certificates
local radixtree_router
local radixtree_router_ver
Expand All @@ -44,7 +45,7 @@ local function create_router(ssl_items)
local idx = 0

for _, ssl in config_util.iterate_values(ssl_items) do
if ssl.value ~= nil and
if ssl.value ~= nil and ssl.value.type == "server" and
(ssl.value.status == nil or ssl.value.status == 1) then -- compatible with old version

local j = 0
Expand Down Expand Up @@ -261,4 +262,19 @@ function _M.init_worker()
end


function _M.get_by_id(ssl_id)
local ssl
local ssls = core.config.fetch_created_obj("/ssl")
if ssls then
ssl = ssls:get(tostring(ssl_id))
end

if not ssl then
return nil
end

return ssl.value
end


return _M
37 changes: 35 additions & 2 deletions apisix/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -330,14 +330,24 @@ function _M.set_by_route(route, api_ctx)

local scheme = up_conf.scheme
if (scheme == "https" or scheme == "grpcs") and up_conf.tls then

local client_cert, client_key
if up_conf.tls.client_cert_id then
client_cert = api_ctx.upstream_ssl.cert
client_key = api_ctx.upstream_ssl.key
else
client_cert = up_conf.tls.client_cert
client_key = up_conf.tls.client_key
end

-- the sni here is just for logging
local sni = api_ctx.var.upstream_host
local cert, err = apisix_ssl.fetch_cert(sni, up_conf.tls.client_cert)
local cert, err = apisix_ssl.fetch_cert(sni, client_cert)
if not ok then
return 503, err
end

local key, err = apisix_ssl.fetch_pkey(sni, up_conf.tls.client_key)
local key, err = apisix_ssl.fetch_pkey(sni, client_key)
if not ok then
return 503, err
end
Expand Down Expand Up @@ -415,6 +425,29 @@ local function check_upstream_conf(in_dp, conf)
return false, "invalid configuration: " .. err
end

local ssl_id = conf.tls and conf.tls.client_cert_id
if ssl_id then
local key = "/ssl/" .. ssl_id
local res, err = core.etcd.get(key)
if not res then
return nil, "failed to fetch ssl info by "
.. "ssl id [" .. ssl_id .. "]: " .. err
end

if res.status ~= 200 then
return nil, "failed to fetch ssl info by "
.. "ssl id [" .. ssl_id .. "], "
.. "response code: " .. res.status
end
if res.body and res.body.node and
res.body.node.value and res.body.node.value.type ~= "client" then

return nil, "failed to fetch ssl info by "
.. "ssl id [" .. ssl_id .. "], "
.. "wrong ssl type"
end
end

-- encrypt the key in the admin
if conf.tls and conf.tls.client_key then
conf.tls.client_key = apisix_ssl.aes_encrypt_pkey(conf.tls.client_key)
Expand Down
8 changes: 6 additions & 2 deletions docs/en/latest/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,9 @@ In addition to the equalization algorithm selections, Upstream also supports pas
| labels | optional | Attributes of the Upstream specified as key-value pairs. | {"version":"v2","build":"16","env":"production"} |
| create_time | optional | Epoch timestamp (in seconds) of the created time. If missing, this field will be populated automatically. | 1602883670 |
| update_time | optional | Epoch timestamp (in seconds) of the updated time. If missing, this field will be populated automatically. | 1602883670 |
| tls.client_cert | optional | Sets the client certificate while connecting to a TLS Upstream. | |
| tls.client_key | optional | Sets the client private key while connecting to a TLS Upstream. | |
| tls.client_cert | optional, can't be used with `tls.client_cert_id` | Sets the client certificate while connecting to a TLS Upstream. | |
| tls.client_key | optional, can't be used with `tls.client_cert_id` | Sets the client private key while connecting to a TLS Upstream. | |
| tls.client_cert_id | optional, can't be used with `tls.client_cert` and `tls.client_key` | Set the referenced [SSL](#ssl) id. | |
Copy link
Member

Choose a reason for hiding this comment

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

@tokers pls confirm this first

Copy link
Contributor

Choose a reason for hiding this comment

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

No problem but currently it looks strange as we use an SSL object while the name is client_cert_id.

| keepalive_pool.size | optional | Sets `keepalive` directive dynamically. | |
| keepalive_pool.idle_timeout | optional | Sets `keepalive_timeout` directive dynamically. | |
| keepalive_pool.requests | optional | Sets `keepalive_requests` directive dynamically. | |
Expand Down Expand Up @@ -570,6 +571,8 @@ You can set the `scheme` to `tls`, which means "TLS over TCP".

To use mTLS to communicate with Upstream, you can use the `tls.client_cert/key` in the same format as SSL's `cert` and `key` fields.

Or you can reference SSL object by `tls.client_cert_id` to set SSL cert and key. The SSL object can be referenced only if the `type` field is `client`. Only `cert` and `key` will be used in the SSL object.
soulbird marked this conversation as resolved.
Show resolved Hide resolved

To allow Upstream to have a separate connection pool, use `keepalive_pool`. It can be configured by modifying its child fields.

Example Configuration:
Expand Down Expand Up @@ -789,6 +792,7 @@ Currently, the response is returned from etcd.
| labels | False | Match Rules | Attributes of the resource specified as key-value pairs. | {"version":"v2","build":"16","env":"production"} |
| create_time | False | Auxiliary | Epoch timestamp (in seconds) of the created time. If missing, this field will be populated automatically. | 1602883670 |
| update_time | False | Auxiliary | Epoch timestamp (in seconds) of the updated time. If missing, this field will be populated automatically. | 1602883670 |
| type | False | Certificate type | Identifies the type of certificate, default `server`. | `client` Indicates that the certificate is a client certificate, which is used when APISIX accesses the upstream; `server` Indicates that the certificate is a server-side certificate, which is used by APISIX when verifying client requests. |
soulbird marked this conversation as resolved.
Show resolved Hide resolved
| status | False | Auxiliary | Enables the current SSL. Set to `1` (enabled) by default. | `1` to enable, `0` to disable |

Example Configuration:
Expand Down
Loading