diff --git a/CHANGELOG.md b/CHANGELOG.md index c5356f746..db04e435d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/gateway/src/apicast/http_proxy.lua b/gateway/src/apicast/http_proxy.lua index 0c868365b..0481b2a9c 100644 --- a/gateway/src/apicast/http_proxy.lua +++ b/gateway/src/apicast/http_proxy.lua @@ -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) @@ -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) diff --git a/gateway/src/apicast/upstream.lua b/gateway/src/apicast/upstream.lua index 0feb3420b..ef2097dc9 100644 --- a/gateway/src/apicast/upstream.lua +++ b/gateway/src/apicast/upstream.lua @@ -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() diff --git a/gateway/src/resty/http/proxy.lua b/gateway/src/resty/http/proxy.lua index 637ed0e3e..e02132c10 100644 --- a/gateway/src/resty/http/proxy.lua +++ b/gateway/src/resty/http/proxy.lua @@ -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 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 @@ -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 @@ -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 + end end -- Connect via proxy @@ -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. diff --git a/t/apicast-policy-http-proxy.t b/t/apicast-policy-http-proxy.t index 52b442f8c..9b437ec7e 100644 --- a/t/apicast-policy-http-proxy.t +++ b/t/apicast-policy-http-proxy.t @@ -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 +< $ENV{TEST_NGINX_HTTPS_PROXY}, + 'BACKEND_ENDPOINT_OVERRIDE' => "https://test-backend.lvh.me:$ENV{TEST_NGINX_RANDOM_PORT}" +) +--- configuration random_port env eval +<