diff --git a/CHANGELOG.md b/CHANGELOG.md index ee51728c1..699316a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - New content_caching Prometheus metric [THREESCALE-5439](https://issues.jboss.org/browse/THREESCALE-5439) [PR #1203](https://github.com/3scale/APIcast/pull/1203) - Added Camel policy [PR #1193](https://github.com/3scale/APIcast/pull/1193) [THREESCALE-4867](https://issues.jboss.org/browse/THREESCALE-4867) +- Support Proxy Protocol [PR #1211](https://github.com/3scale/APIcast/pull/1211) [THREESCALE-5366](https://issues.redhat.com/browse/THREESCALE-5366) ## [3.8.0] - 2020-03-24 diff --git a/doc/parameters.md b/doc/parameters.md index 9d92e65cf..ba83d1fae 100644 --- a/doc/parameters.md +++ b/doc/parameters.md @@ -424,6 +424,22 @@ Defines a HTTP proxy to be used for connecting to HTTPS services. Authentication Defines a comma-separated list of hostnames and domain names for which the requests should not be proxied. Setting to a single `*` character, which matches all hosts, effectively disables the proxy. +### `APICAST_HTTP_PROXY_PROTOCOL` + +**Default:** false +**Values:** boolean +**Example:** "true" + +This parameter enables the Proxy Protocol for the HTTP listener. + +### `APICAST_HTTPS_PROXY_PROTOCOL` + +**Default:** false +**Values:** boolean +**Example:** "true" + +This parameter enables the Proxy Protocol for the HTTPS listener. + ### `APICAST_EXTENDED_METRICS` **Default:** false diff --git a/gateway/http.d/apicast.conf.liquid b/gateway/http.d/apicast.conf.liquid index 6283f09ef..cc909efbe 100644 --- a/gateway/http.d/apicast.conf.liquid +++ b/gateway/http.d/apicast.conf.liquid @@ -56,14 +56,17 @@ server { {%- assign http_port = port.apicast | default: 8080 %} {%- assign https_port = env.APICAST_HTTPS_PORT %} + {%- assign http_proxy_protocol = env.APICAST_HTTP_PROXY_PROTOCOL %} + {%- assign https_proxy_protocol = env.APICAST_HTTPS_PROXY_PROTOCOL %} {% if http_port != https_port -%} - listen {{ http_port }}; + listen {{ http_port }} {% if http_proxy_protocol %}proxy_protocol {% endif %}; {% endif %} {% if https_port -%} - listen {{ https_port }} ssl http2; - ssl_protocols TLSv1.2 TLSv1.3; + listen {{ https_port }} ssl http2 {% if https_proxy_protocol %}proxy_protocol{% endif %}; + + ssl_protocols TLSv1.2 TLSv1.3; {%- assign https_certificate = env.APICAST_HTTPS_CERTIFICATE -%} ssl_certificate {% if https_certificate -%} {{ https_certificate }} diff --git a/gateway/src/apicast/policy/ip_check/apicast-policy.json b/gateway/src/apicast/policy/ip_check/apicast-policy.json index eefe86b2d..00e9d0956 100644 --- a/gateway/src/apicast/policy/ip_check/apicast-policy.json +++ b/gateway/src/apicast/policy/ip_check/apicast-policy.json @@ -55,6 +55,10 @@ "enum": ["X-Real-IP"], "title": "Get the IP from the X-Real-IP header" }, + { + "enum": ["proxy_protocol_addr"], + "title": "Get the IP from the proxy_protocol_addr variable" + }, { "enum": ["last_caller"], "title": "Use the IP of the last caller" diff --git a/gateway/src/apicast/policy/ip_check/client_ip.lua b/gateway/src/apicast/policy/ip_check/client_ip.lua index 45fa16aa9..aa561562f 100644 --- a/gateway/src/apicast/policy/ip_check/client_ip.lua +++ b/gateway/src/apicast/policy/ip_check/client_ip.lua @@ -33,10 +33,15 @@ local function ip_from_x_forwarded_for_header() return uri.host end +local function ip_from_proxy_protocol_addr_variable() + return ngx.var.proxy_protocol_addr +end + local get_ip_func = { last_caller = last_caller_ip, ["X-Real-IP"] = ip_from_x_real_ip_header, - ["X-Forwarded-For"] = ip_from_x_forwarded_for_header + ["X-Forwarded-For"] = ip_from_x_forwarded_for_header, + ["proxy_protocol_addr"] = ip_from_proxy_protocol_addr_variable } function _M.get_from(sources) diff --git a/spec/policy/ip_check/client_ip_spec.lua b/spec/policy/ip_check/client_ip_spec.lua index b8c65b397..d9cca02e1 100644 --- a/spec/policy/ip_check/client_ip_spec.lua +++ b/spec/policy/ip_check/client_ip_spec.lua @@ -117,6 +117,16 @@ describe('ClientIP', function() end) end) + describe('when the source is the proxy_protocol_addr variable', function() + it('returns the IP in ngx.var.proxy_protocol_addr', function() + ngx.var = { proxy_protocol_addr = '1.2.3.4' } + + local ip = client_ip.get_from({ 'proxy_protocol_addr' }) + + assert.equals('1.2.3.4', ip) + end) + end) + describe('when no source is given', function() it('returns nil', function() local ip = client_ip.get_from({}) diff --git a/spec/policy/ip_check/ip_check_spec.lua b/spec/policy/ip_check/ip_check_spec.lua index 4ec90dc6f..b5965e332 100644 --- a/spec/policy/ip_check/ip_check_spec.lua +++ b/spec/policy/ip_check/ip_check_spec.lua @@ -2,7 +2,7 @@ local IpCheckPolicy = require('apicast.policy.ip_check') local ClientIP = require('apicast.policy.ip_check.client_ip') local iputils = require("resty.iputils") -describe('Headers policy', function() +describe('IP Check policy', function() before_each(function() stub(ngx, 'say') stub(ngx, 'exit') diff --git a/t/proxy-protocol.t b/t/proxy-protocol.t new file mode 100644 index 000000000..86d6db03f --- /dev/null +++ b/t/proxy-protocol.t @@ -0,0 +1,189 @@ +use lib 't'; +use Test::APIcast::Blackbox 'no_plan'; + +# These test are a bit difficult to understand. Due to the Nginx is set to listen +# proxy_protocol, all backends upstream need to use another port, if not the +# backend client and API will fail, this is why a new server is added on +# --backend test section +# +# Additionally, this is the reason why the IP Check policy is tested here. + + +require("policies.pl"); +repeat_each(3); +$ENV{TEST_NGINX_HTML_DIR} ||= "$Test::Nginx::Util::ServRoot/html"; +run_tests(); + +__DATA__ + +=== TEST 1: Simple proxy-procol connection +--- init eval +$Test::Nginx::Util::BACKEND_PORT = Test::APIcast::get_random_port(); +--- env eval +( + "BACKEND_ENDPOINT_OVERRIDE"=> "http://127.0.0.1:$Test::Nginx::Util::BACKEND_PORT", + "APICAST_HTTP_PROXY_PROTOCOL" => "true" +) +--- configuration eval +< "http://127.0.0.1:$Test::Nginx::Util::BACKEND_PORT", + "APICAST_HTTP_PROXY_PROTOCOL" => "true" +) +--- configuration eval +<