|
42 | 42 | end |
43 | 43 |
|
44 | 44 | local set_stream_upstream_tls |
| 45 | +local set_stream_upstream_cert_and_key |
45 | 46 | if not is_http then |
46 | 47 | local ok, apisix_ngx_stream_upstream = pcall(require, "resty.apisix.stream.upstream") |
47 | 48 | if ok then |
48 | 49 | set_stream_upstream_tls = apisix_ngx_stream_upstream.set_tls |
49 | | - else |
| 50 | + set_stream_upstream_cert_and_key = apisix_ngx_stream_upstream.set_cert_and_key |
| 51 | + end |
| 52 | + -- guard each function independently: an older runtime may expose the module |
| 53 | + -- (set_tls) without the newer mTLS C-API (set_cert_and_key) |
| 54 | + if not set_stream_upstream_tls then |
50 | 55 | set_stream_upstream_tls = function () |
51 | 56 | return nil, "need to build APISIX-Runtime to support TLS over TCP upstream" |
52 | 57 | end |
53 | 58 | end |
| 59 | + if not set_stream_upstream_cert_and_key then |
| 60 | + set_stream_upstream_cert_and_key = function () |
| 61 | + return nil, "need to build APISIX-Runtime to support upstream mTLS over TCP" |
| 62 | + end |
| 63 | + end |
54 | 64 | end |
55 | 65 |
|
56 | 66 |
|
@@ -160,6 +170,54 @@ local function fill_node_info(up_conf, scheme, is_stream) |
160 | 170 | end |
161 | 171 |
|
162 | 172 |
|
| 173 | +-- Set upstream client certificate (mTLS) for the stream (L4) subsystem. |
| 174 | +-- Mirrors the http subsystem: the cert/key are parsed and cached once (the key |
| 175 | +-- is AES-decrypted at rest by fetch_pkey) and applied to the upstream SSL |
| 176 | +-- handshake through the apisix-nginx-module stream C API, so the plaintext key |
| 177 | +-- is never stringified into an nginx variable. |
| 178 | +local function set_stream_upstream_client_cert(api_ctx, up_conf) |
| 179 | + local tls = up_conf.tls |
| 180 | + if not (tls and (tls.client_cert or tls.client_cert_id)) then |
| 181 | + return true |
| 182 | + end |
| 183 | + |
| 184 | + local client_cert, client_key |
| 185 | + if tls.client_cert_id then |
| 186 | + if not api_ctx.upstream_ssl then |
| 187 | + return nil, "failed to find upstream ssl object for client_cert_id" |
| 188 | + end |
| 189 | + client_cert = api_ctx.upstream_ssl.cert |
| 190 | + client_key = api_ctx.upstream_ssl.key |
| 191 | + else |
| 192 | + client_cert = tls.client_cert |
| 193 | + client_key = tls.client_key |
| 194 | + end |
| 195 | + |
| 196 | + if not (client_cert and client_key) then |
| 197 | + return nil, "missing client certificate or key for upstream mTLS" |
| 198 | + end |
| 199 | + |
| 200 | + -- the sni here is just for logging |
| 201 | + local sni = api_ctx.var.upstream_host |
| 202 | + local cert, err = apisix_ssl.fetch_cert(sni, client_cert) |
| 203 | + if not cert then |
| 204 | + return nil, err |
| 205 | + end |
| 206 | + |
| 207 | + local key, err = apisix_ssl.fetch_pkey(sni, client_key) |
| 208 | + if not key then |
| 209 | + return nil, err |
| 210 | + end |
| 211 | + |
| 212 | + local ok, err = set_stream_upstream_cert_and_key(cert, key) |
| 213 | + if not ok then |
| 214 | + return nil, err |
| 215 | + end |
| 216 | + |
| 217 | + return true |
| 218 | +end |
| 219 | + |
| 220 | + |
163 | 221 | function _M.set_by_route(route, api_ctx) |
164 | 222 | if api_ctx.upstream_conf then |
165 | 223 | -- upstream_conf has been set by traffic-split plugin |
@@ -246,6 +304,11 @@ function _M.set_by_route(route, api_ctx) |
246 | 304 | if sni then |
247 | 305 | ngx_var.upstream_sni = sni |
248 | 306 | end |
| 307 | + |
| 308 | + local ok, err = set_stream_upstream_client_cert(api_ctx, up_conf) |
| 309 | + if not ok then |
| 310 | + return 503, err |
| 311 | + end |
249 | 312 | end |
250 | 313 | local node_ver = resource.get_nodes_ver(up_conf.resource_key) |
251 | 314 | local resource_version = upstream_util.version(up_conf.resource_version, |
|
0 commit comments