Skip to content

Commit

Permalink
[proxy] Add support to set client certificate/key when sending reques…
Browse files Browse the repository at this point in the history
…t via proxy
  • Loading branch information
tkan145 committed Oct 16, 2024
1 parent 09c1c25 commit a708c22
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Fixed Conditional policy evaluating incorrectly: second policy in policy chain that implement export() always triggers [PR #1485](https://github.com/3scale/APIcast/pull/1485) [THREESCALE-9320](https://issues.redhat.com/browse/THREESCALE-9320)
- Fix APIcast using stale configuration for deleted products [PR #1488](https://github.com/3scale/APIcast/pull/1488) [THREESCALE-10130](https://issues.redhat.com/browse/THREESCALE-10130)
- Fixed Mutual TLS between APIcast and the Backend API fails when using a Forward Proxy [PR #1499](https://github.com/3scale/APIcast/pull/1499) [THREESCALE-5105](https://issues.redhat.com/browse/THREESCALE-5105)

### Added

Expand Down
7 changes: 3 additions & 4 deletions gateway/src/apicast/http_proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,7 @@ local function forward_https_request(proxy_uri, uri, proxy_opts)
path = format('%s%s%s', ngx.var.uri, ngx.var.is_args, ngx.var.query_string or ''),
body = body,
proxy_uri = proxy_uri,
proxy_auth = opts.proxy_auth,
upstream_connection_opts = opts.upstream_connection_opts,
skip_https_connect = opts.skip_https_connect
proxy_options = opts
}

local httpc, err = http_proxy.new(request)
Expand Down Expand Up @@ -226,7 +224,8 @@ function _M.request(upstream, proxy_uri)
proxy_auth = proxy_auth,
skip_https_connect = upstream.skip_https_connect,
request_unbuffered = upstream.request_unbuffered,
upstream_connection_opts = upstream.upstream_connection_opts
upstream_connection_opts = upstream.upstream_connection_opts,
upstream_ssl = upstream.upstream_ssl
}

forward_https_request(proxy_uri, uri, proxy_opts)
Expand Down
2 changes: 2 additions & 0 deletions gateway/src/apicast/policy/upstream_mtls/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ When using http forms and file upload
This policy overwrites `APICAST_PROXY_HTTPS_CERTIFICATE_KEY` and
`APICAST_PROXY_HTTPS_CERTIFICATE` values and it uses the certificates set by
the policy, so those environment variables will have no effect.

This policy is not compatible with Camel proxy.
5 changes: 5 additions & 0 deletions gateway/src/apicast/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ function _M:call(context)

self.request_unbuffered = context.request_unbuffered
self.upstream_connection_opts = context.upstream_connection_opts
self.upstream_ssl = {
ssl_verify = context.upstream_verify,
ssl_client_cert = context.upstream_certificate,
ssl_client_priv_key = context.upstream_key
}
http_proxy.request(self, proxy_uri)
else
local err = self:rewrite_request()
Expand Down
13 changes: 9 additions & 4 deletions gateway/src/resty/http/proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ end
local function connect(request)
request = request or { }
local httpc = http.new()
local proxy_options = request.proxy_options or {}

if request.upstream_connection_opts then
local con_opts = request.upstream_connection_opts
if proxy_options.upstream_connection_opts then
local con_opts = request.proxy_options.upstream_connection_opts

Check warning on line 38 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L38

Added line #L38 was not covered by tests
ngx.log(ngx.DEBUG, 'setting timeouts (secs), connect_timeout: ', con_opts.connect_timeout,
' send_timeout: ', con_opts.send_timeout, ' read_timeout: ', con_opts.read_timeout)
-- lua-resty-http uses nginx API for lua sockets
Expand All @@ -51,7 +52,7 @@ local function connect(request)
local scheme = uri.scheme
local host = uri.host
local port = default_port(uri)
local skip_https_connect = request.skip_https_connect
local skip_https_connect = proxy_options.skip_https_connect

-- set ssl_verify: lua-resty-http set ssl_verify to true by default if scheme is https, whereas
-- openresty treat nil as false, so we need to explicitly set ssl_verify to false if nil
Expand All @@ -68,6 +69,10 @@ local function connect(request)
if scheme == 'https' then
options.ssl_server_name = host
options.ssl_verify = ssl_verify
if proxy_options.upstream_ssl then
options.ssl_client_cert = proxy_options.upstream_ssl.ssl_client_cert
options.ssl_client_priv_key = proxy_options.upstream_ssl.ssl_client_priv_key

Check warning on line 74 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L73-L74

Added lines #L73 - L74 were not covered by tests
end
end

-- Connect via proxy
Expand All @@ -79,7 +84,7 @@ local function connect(request)
end

local proxy_url = format("%s://%s:%s", proxy_uri.scheme, proxy_uri.host, proxy_uri.port)
local proxy_auth = request.proxy_auth
local proxy_auth = proxy_options.proxy_auth

if scheme == 'http' then
-- Used by http_ng module to send request to 3scale backend through proxy.
Expand Down
136 changes: 136 additions & 0 deletions t/apicast-policy-http-proxy.t
Original file line number Diff line number Diff line change
Expand Up @@ -1143,3 +1143,139 @@ POST /test?user_key=
--- no_error_log env
["[error]",
"using proxy: $TEST_NGINX_HTTP_PROXY "]
=== TEST 18: MTLS connection to upstream via proxy failed
--- configuration random_port env
{
"services": [
{
"backend_version": 1,
"proxy": {
"api_backend": "https://test-upstream.lvh.me:$TEST_NGINX_RANDOM_PORT",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 }
],
"policy_chain": [
{
"name": "apicast.policy.apicast"
},
{
"name": "apicast.policy.http_proxy",
"configuration": {
"https_proxy": "$TEST_NGINX_HTTPS_PROXY"
}
}
]
}
}
]
}
--- backend env
server_name test-backend.lvh.me;
listen $TEST_NGINX_RANDOM_PORT ssl;
ssl_certificate $TEST_NGINX_SERVER_ROOT/html/server.crt;
ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/server.key;
location /transactions/authrep.xml {
content_by_lua_block {
ngx.exit(ngx.OK)
}
}
--- upstream env
server_name test-upstream.lvh.me;
listen $TEST_NGINX_RANDOM_PORT ssl;
ssl_certificate $TEST_NGINX_SERVER_ROOT/html/server.crt;
ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/server.key;
ssl_client_certificate $TEST_NGINX_SERVER_ROOT/html/client.crt;
ssl_verify_client on;
location /test {
echo 'ssl_client_s_dn: \$ssl_client_s_dn';
echo 'ssl_client_i_dn: \$ssl_client_i_dn';
}
--- request
GET /test?user_key=value
--- error_code: 400
--- error_log env
using proxy: $TEST_NGINX_HTTPS_PROXY
proxy request: CONNECT test-upstream.lvh.me:$TEST_NGINX_RANDOM_PORT HTTP/1.1
client sent no required SSL certificate while reading client request headers
--- no_error_log
[error]
--- user_files fixture=mutual_ssl.pl eval
=== TEST 19: MTLS connection to upstream via proxy when certificates are provided
--- configuration random_port env eval
<<EOF
{
"services": [
{
"backend_version": 1,
"proxy": {
"api_backend": "https://test-upstream.lvh.me:$TEST_NGINX_RANDOM_PORT",
"proxy_rules": [
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 }
],
"policy_chain": [
{
"name": "apicast.policy.apicast"
},
{
"name": "apicast.policy.upstream_mtls",
"configuration": {
"certificate": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.crt",
"certificate_type": "path",
"certificate_key": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.key",
"certificate_key_type": "path",
"ca_certificates": [
"$Test::Nginx::Util::UPSTREAM_CA_CERT"
],
"verify": true
}
},
{
"name": "apicast.policy.http_proxy",
"configuration": {
"https_proxy": "$TEST_NGINX_HTTPS_PROXY"
}
}
]
}
}
]
}
EOF
--- backend env
server_name test-backend.lvh.me;
listen $TEST_NGINX_RANDOM_PORT ssl;
ssl_certificate $TEST_NGINX_SERVER_ROOT/html/server.crt;
ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/server.key;
location /transactions/authrep.xml {
content_by_lua_block {
ngx.exit(ngx.OK)
}
}
--- upstream env
server_name test-upstream.lvh.me;
listen $TEST_NGINX_RANDOM_PORT ssl;
ssl_certificate $TEST_NGINX_SERVER_ROOT/html/server.crt;
ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/server.key;
ssl_client_certificate $TEST_NGINX_SERVER_ROOT/html/client.crt;
ssl_verify_client on;
location /test {
echo 'ssl_client_s_dn: $ssl_client_s_dn';
echo 'ssl_client_i_dn: $ssl_client_i_dn';
}
--- request
GET /test?user_key=value
--- response_body
ssl_client_s_dn: CN=localhost,OU=APIcast,O=3scale
ssl_client_i_dn: CN=localhost,OU=APIcast,O=3scale
--- error_code: 200
--- error_log env
using proxy: $TEST_NGINX_HTTPS_PROXY
proxy request: CONNECT test-upstream.lvh.me:$TEST_NGINX_RANDOM_PORT HTTP/1.1
--- no_error_log
[error]
--- user_files fixture=mutual_ssl.pl eval
78 changes: 78 additions & 0 deletions t/http-proxy.t
Original file line number Diff line number Diff line change
Expand Up @@ -2126,3 +2126,81 @@ GET /?user_key=value
yay, api backend: test-upstream.lvh.me:$TEST_NGINX_SERVER_PORT
--- error_code: 200
--- no_error_log
=== TEST 37: HTTPS_PROXY with mtls policy
--- env random_port eval
(
'https_proxy' => $ENV{TEST_NGINX_HTTPS_PROXY},
'BACKEND_ENDPOINT_OVERRIDE' => "https://test-backend.lvh.me:$ENV{TEST_NGINX_RANDOM_PORT}"
)
--- configuration random_port env eval
<<EOF
{
"services": [
{
"backend_version": 1,
"proxy": {
"api_backend": "https://test-upstream.lvh.me:$TEST_NGINX_RANDOM_PORT",
"proxy_rules": [
{ "pattern": "/test", "http_method": "GET", "metric_system_name": "hits", "delta": 2 }
],
"policy_chain": [
{
"name": "apicast.policy.upstream_mtls",
"configuration": {
"certificate": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.crt",
"certificate_type": "path",
"certificate_key": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.key",
"certificate_key_type": "path",
"ca_certificates": [
"$Test::Nginx::Util::UPSTREAM_CA_CERT"
],
"verify": true
}
},
{
"name": "apicast",
"version": "builtin",
"configuration": {}
}
]
}
}
]
}
EOF
--- backend env
server_name test-backend.lvh.me;
listen $TEST_NGINX_RANDOM_PORT ssl;
ssl_certificate $TEST_NGINX_SERVER_ROOT/html/server.crt;
ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/server.key;
location /transactions/authrep.xml {
content_by_lua_block {
ngx.exit(ngx.OK)
}
}
--- upstream env
server_name test-upstream.lvh.me;
listen $TEST_NGINX_RANDOM_PORT ssl;
ssl_certificate $TEST_NGINX_SERVER_ROOT/html/server.crt;
ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/server.key;
ssl_client_certificate $TEST_NGINX_SERVER_ROOT/html/client.crt;
ssl_verify_client on;
location /test {
echo 'ssl_client_s_dn: $ssl_client_s_dn';
echo 'ssl_client_i_dn: $ssl_client_i_dn';
}
--- request
GET /test?user_key=value
--- response_body
ssl_client_s_dn: CN=localhost,OU=APIcast,O=3scale
ssl_client_i_dn: CN=localhost,OU=APIcast,O=3scale
--- error_code: 200
--- error_log env
using proxy: $TEST_NGINX_HTTPS_PROXY
proxy request: CONNECT test-upstream.lvh.me:$TEST_NGINX_RANDOM_PORT HTTP/1.1
--- no_error_log
[error]
--- user_files fixture=mutual_ssl.pl eval

0 comments on commit a708c22

Please sign in to comment.