Skip to content

Commit

Permalink
feat(ssl): support get upstream cert from ssl object (apache#7221)
Browse files Browse the repository at this point in the history
Co-authored-by: soulbird <zhaothree@gmail.com>
  • Loading branch information
2 people authored and Liu-Junlin committed Nov 4, 2022
1 parent e034d21 commit 4ab6192
Show file tree
Hide file tree
Showing 12 changed files with 477 additions and 21 deletions.
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 @@ -497,6 +497,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
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 @@ -333,14 +333,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 @@ -418,6 +428,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. | |
| 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`, otherwise the request will be rejected by APISIX. In addition, only `cert` and `key` will be used in the SSL object.

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 | Auxiliary | 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. |
| status | False | Auxiliary | Enables the current SSL. Set to `1` (enabled) by default. | `1` to enable, `0` to disable |

Example Configuration:
Expand Down
Loading

0 comments on commit 4ab6192

Please sign in to comment.