Skip to content

Commit 6658589

Browse files
authored
feat: support set ssl_trusted_store for upstream (#95)
1 parent cadf995 commit 6658589

File tree

5 files changed

+234
-4
lines changed

5 files changed

+234
-4
lines changed

.github/workflows/ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
run: |
3131
sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl luarocks libpcre3 libpcre3-dev zlib1g-dev
3232
sudo luarocks install lua-resty-http > build.log 2>&1 || (cat build.log && exit 1)
33+
sudo luarocks install lua-resty-openssl > build.log 2>&1 || (cat build.log && exit 1)
3334
3435
- name: Before install
3536
run: |

lib/resty/apisix/upstream.lua

+41-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
local ffi = require("ffi")
22
local base = require("resty.core.base")
33
local get_request = base.get_request
4+
local get_phase = ngx.get_phase
45
local C = ffi.C
56
local NGX_ERROR = ngx.ERROR
7+
local NGX_OK = ngx.OK
68

79

810
base.allows_subsystem("http")
911

1012

1113
ffi.cdef([[
1214
typedef intptr_t ngx_int_t;
13-
ngx_int_t
14-
ngx_http_apisix_upstream_set_cert_and_key(ngx_http_request_t *r, void *cert, void *key);
15+
ngx_int_t ngx_http_apisix_upstream_set_cert_and_key(ngx_http_request_t *r, void *cert, void *key);
16+
ngx_int_t ngx_http_apisix_upstream_set_ssl_trusted_store(ngx_http_request_t *r, void *store);
1517
]])
1618
local _M = {}
1719

@@ -30,5 +32,42 @@ function _M.set_cert_and_key(cert, key)
3032
return true
3133
end
3234

35+
local set_ssl_trusted_store
36+
do
37+
local ALLOWED_PHASES = {
38+
['rewrite'] = true,
39+
['balancer'] = true,
40+
['access'] = true,
41+
['preread'] = true,
42+
}
43+
44+
local openssl_x509_store = require "resty.openssl.x509.store"
45+
function set_ssl_trusted_store(store)
46+
if not ALLOWED_PHASES[get_phase()] then
47+
error("API disabled in the current context", 2)
48+
end
49+
50+
if not openssl_x509_store.istype(store) then
51+
error("store expects a resty.openssl.x509.store" ..
52+
" object but get " .. type(store), 2)
53+
end
54+
55+
local r = get_request()
56+
57+
local ret = C.ngx_http_apisix_upstream_set_ssl_trusted_store(
58+
r, store.ctx)
59+
if ret == NGX_OK then
60+
return true
61+
end
62+
63+
if ret == NGX_ERROR then
64+
return nil, "error while setting upstream trusted store"
65+
end
66+
67+
error("unknown return code: " .. tostring(ret))
68+
end
69+
end
70+
_M.set_ssl_trusted_store = set_ssl_trusted_store
71+
3372

3473
return _M

src/ngx_http_apisix_module.c

+60-2
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ ngx_http_apisix_cleanup_cert_and_key(ngx_http_apisix_ctx_t *ctx)
158158
ctx->upstream_pkey = NULL;
159159
}
160160
}
161+
162+
static void
163+
ngx_http_apisix_cleanup_trusted_store(ngx_http_apisix_ctx_t *ctx)
164+
{
165+
if (ctx->upstream_trusted_store != NULL) {
166+
X509_STORE_free(ctx->upstream_trusted_store);
167+
ctx->upstream_trusted_store = NULL;
168+
}
169+
}
161170
#endif
162171

163172

@@ -168,6 +177,7 @@ ngx_http_apisix_cleanup(void *data)
168177

169178
#if (NGX_HTTP_SSL)
170179
ngx_http_apisix_cleanup_cert_and_key(ctx);
180+
ngx_http_apisix_cleanup_trusted_store(ctx);
171181
#endif
172182
}
173183

@@ -250,6 +260,41 @@ ngx_http_apisix_upstream_set_cert_and_key(ngx_http_request_t *r,
250260
return NGX_ERROR;
251261
}
252262

263+
ngx_int_t
264+
ngx_http_apisix_upstream_set_ssl_trusted_store(ngx_http_request_t *r, void *data)
265+
{
266+
X509_STORE *store = data;
267+
ngx_http_apisix_ctx_t *ctx;
268+
269+
if (store == NULL) {
270+
return NGX_ERROR;
271+
}
272+
273+
ctx = ngx_http_apisix_get_module_ctx(r);
274+
275+
if (ctx == NULL) {
276+
return NGX_ERROR;
277+
}
278+
279+
if (ctx->upstream_trusted_store != NULL) {
280+
ngx_http_apisix_cleanup_trusted_store(ctx);
281+
}
282+
283+
if (X509_STORE_up_ref(store) == 0) {
284+
goto failed;
285+
}
286+
287+
ctx->upstream_trusted_store = store;
288+
289+
return NGX_OK;
290+
291+
failed:
292+
293+
ngx_http_apisix_flush_ssl_error();
294+
295+
return NGX_ERROR;
296+
}
297+
253298

254299
void
255300
ngx_http_apisix_set_upstream_ssl(ngx_http_request_t *r, ngx_connection_t *c)
@@ -258,6 +303,7 @@ ngx_http_apisix_set_upstream_ssl(ngx_http_request_t *r, ngx_connection_t *c)
258303
ngx_http_apisix_ctx_t *ctx;
259304
STACK_OF(X509) *cert;
260305
EVP_PKEY *pkey;
306+
X509_STORE *store;
261307
X509 *x509;
262308
#ifdef OPENSSL_IS_BORINGSSL
263309
size_t i;
@@ -275,8 +321,9 @@ ngx_http_apisix_set_upstream_ssl(ngx_http_request_t *r, ngx_connection_t *c)
275321
}
276322

277323
if (ctx->upstream_cert != NULL) {
278-
cert = ctx->upstream_cert;
279-
pkey = ctx->upstream_pkey;
324+
cert = ctx->upstream_cert;
325+
pkey = ctx->upstream_pkey;
326+
store = ctx->upstream_trusted_store;
280327

281328
if (sk_X509_num(cert) < 1) {
282329
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
@@ -317,6 +364,17 @@ ngx_http_apisix_set_upstream_ssl(ngx_http_request_t *r, ngx_connection_t *c)
317364
"SSL_use_PrivateKey() failed");
318365
goto failed;
319366
}
367+
368+
if (store != NULL) {
369+
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
370+
"overriding upstream SSL trusted store");
371+
372+
if (SSL_set1_verify_cert_store(sc, store) == 0) {
373+
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0,
374+
"SSL_set1_verify_cert_store() failed");
375+
goto failed;
376+
}
377+
}
320378
}
321379

322380
return;

src/ngx_http_apisix_module.h

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ typedef struct {
2222
typedef struct {
2323
STACK_OF(X509) *upstream_cert;
2424
EVP_PKEY *upstream_pkey;
25+
X509_STORE *upstream_trusted_store;
2526

2627
off_t client_max_body_size;
2728

t/upstream_mtls2.t

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use t::APISIX_NGINX 'no_plan';
2+
3+
repeat_each(2);
4+
5+
add_block_preprocessor(sub {
6+
my ($block) = @_;
7+
8+
if (!$block->http_config) {
9+
my $http_config = <<'_EOC_';
10+
server {
11+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
12+
server_name admin.apisix.dev;
13+
ssl_certificate ../../certs/mtls_server.crt;
14+
ssl_certificate_key ../../certs/mtls_server.key;
15+
ssl_client_certificate ../../certs/mtls_ca.crt;
16+
ssl_verify_client on;
17+
18+
server_tokens off;
19+
20+
location /foo {
21+
return 200 'ok\n';
22+
}
23+
}
24+
25+
_EOC_
26+
27+
$block->set_value("http_config", $http_config);
28+
}
29+
});
30+
31+
run_tests;
32+
33+
__DATA__
34+
35+
=== TEST 1: only set client certificate
36+
--- config
37+
location /t {
38+
access_by_lua_block {
39+
local upstream = require("resty.apisix.upstream")
40+
local ssl = require("ngx.ssl")
41+
42+
local f = assert(io.open("t/certs/mtls_client.crt"))
43+
local cert_data = f:read("*a")
44+
f:close()
45+
46+
local cert = assert(ssl.parse_pem_cert(cert_data))
47+
48+
f = assert(io.open("t/certs/mtls_client.key"))
49+
local key_data = f:read("*a")
50+
f:close()
51+
52+
local key = assert(ssl.parse_pem_priv_key(key_data))
53+
54+
local ok, err = upstream.set_cert_and_key(cert, key)
55+
if not ok then
56+
ngx.say("set_cert_and_key failed: ", err)
57+
end
58+
}
59+
60+
proxy_ssl_trusted_certificate ../../certs/mtls_client.crt;
61+
proxy_ssl_verify on;
62+
proxy_ssl_name admin.apisix.dev;
63+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock:/foo;
64+
}
65+
--- error_code: 502
66+
--- error_log
67+
unable to verify the first certificate
68+
69+
70+
71+
=== TEST 2: send valid client certificate
72+
--- config
73+
location /t {
74+
access_by_lua_block {
75+
local upstream = require("resty.apisix.upstream")
76+
local ssl = require("ngx.ssl")
77+
78+
local f = assert(io.open("t/certs/mtls_client.crt"))
79+
local cert_data = f:read("*a")
80+
f:close()
81+
82+
local cert = assert(ssl.parse_pem_cert(cert_data))
83+
84+
f = assert(io.open("t/certs/mtls_client.key"))
85+
local key_data = f:read("*a")
86+
f:close()
87+
88+
local key = assert(ssl.parse_pem_priv_key(key_data))
89+
90+
local ok, err = upstream.set_cert_and_key(cert, key)
91+
if not ok then
92+
ngx.say("set_cert_and_key failed: ", err)
93+
end
94+
95+
f = assert(io.open("t/certs/mtls_ca.crt"))
96+
local ca_data = f:read("*a")
97+
f:close()
98+
99+
local ca_cert = assert(ssl.parse_pem_cert(ca_data))
100+
101+
local openssl_x509_store = require "resty.openssl.x509.store"
102+
local openssl_x509 = require "resty.openssl.x509"
103+
local trust_store, err = openssl_x509_store.new()
104+
if err then
105+
ngx.log(ngx.ERR, "failed to create trust store: ", err)
106+
ngx.exit(500)
107+
end
108+
109+
local x509, err = openssl_x509.new(ca_data, "PEM")
110+
111+
local _, err = trust_store:add(x509)
112+
if err then
113+
ngx.log(ngx.ERR, "failed to add ca cert to trust store: ", err)
114+
ngx.exit(500)
115+
end
116+
117+
local ok, err = upstream.set_ssl_trusted_store(trust_store)
118+
if not ok then
119+
ngx.log(ngx.ERR, "set_ssl_trusted_store failed: ", err)
120+
ngx.exit(500)
121+
end
122+
}
123+
124+
proxy_ssl_trusted_certificate ../../certs/mtls_client.crt;
125+
proxy_ssl_verify on;
126+
proxy_ssl_name admin.apisix.dev;
127+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock:/foo;
128+
}
129+
130+
--- response_body
131+
ok

0 commit comments

Comments
 (0)