From 4d5edb0d646124d5ad0035afa8e9f9e82c497cd3 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 3 Jun 2020 21:46:51 +0700 Subject: [PATCH] Feature/remove request headers (#822) * config: add RemoveRequestHeaders Currently, we have "set_request_headers" config, which reflects envoy route.Route.RequestHeadersToAdd. This commit add new config "remove_request_headers", which reflects envoy RequestHeadersToRemove. This is also a preparation for future PRs to implement disable user identity in request headers feature. * integration: add test for remove_request_headers * docs: add documentation/changelog for remove_request_headers --- config/policy.go | 5 +++ docs/configuration/readme.md | 18 +++++++++ docs/docs/CHANGELOG.md | 6 +++ integration/manifests/lib/pomerium.libsonnet | 1 + integration/policy_test.go | 32 ++++++++++++++++ internal/controlplane/xds_routes.go | 3 +- internal/controlplane/xds_routes_test.go | 39 ++++++++++++++++++-- 7 files changed, 99 insertions(+), 5 deletions(-) diff --git a/config/policy.go b/config/policy.go index 9d4fb3cf4..1bb25e8dc 100644 --- a/config/policy.go +++ b/config/policy.go @@ -80,6 +80,11 @@ type Policy struct { // value of any existing value of a given header key. SetRequestHeaders map[string]string `mapstructure:"set_request_headers" yaml:"set_request_headers,omitempty"` + // RemoveRequestHeaders removes a collection of headers from a downstream request. + // Note that this has lower priority than `SetRequestHeaders`, if you specify `X-Custom-Header` in both + // `SetRequestHeaders` and `RemoveRequestHeaders`, then the header won't be removed. + RemoveRequestHeaders []string `mapstructure:"remove_request_headers" yaml:"remove_request_headers,omitempty"` + // PreserveHostHeader disables host header rewriting. // // https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header diff --git a/docs/configuration/readme.md b/docs/configuration/readme.md index 3e7b3b329..b81322048 100644 --- a/docs/configuration/readme.md +++ b/docs/configuration/readme.md @@ -995,6 +995,24 @@ Set Request Headers allows you to set static values for given request headers. T X-Your-favorite-authenticating-Proxy: "Pomerium" ``` +### Remove Request Headers + +- Config File Key: `removet_request_headers` +- Type: array of `strings` +- Optional + +Remove Request Headers allows you to remove given request headers. This can be useful if you want to prevent privacy information from being passed to downstream applications. For example: + +```yaml +- from: https://httpbin.corp.example.com + to: https://httpbin.org + allowed_users: + - bdd@pomerium.io + remove_request_headers: + - X-Email + - X-Username +``` + ### To - `yaml`/`json` setting: `to` diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 058a72b46..b8c94d6aa 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.10.0 + +### New + +- config: add remove_request_headers @cuonglm [GH-702] + ## v0.9.0 ### New diff --git a/integration/manifests/lib/pomerium.libsonnet b/integration/manifests/lib/pomerium.libsonnet index 862d29885..d9c7e6a81 100644 --- a/integration/manifests/lib/pomerium.libsonnet +++ b/integration/manifests/lib/pomerium.libsonnet @@ -119,6 +119,7 @@ local PomeriumPolicy = function() std.flattenArrays( set_request_headers: { 'X-Custom-Request-Header': 'custom-request-header-value', }, + remove_request_headers: ['X-Custom-Request-Header-To-Remove'], }, { from: 'http://restricted-' + domain + '.localhost.pomerium.io', diff --git a/integration/policy_test.go b/integration/policy_test.go index 8ab173200..6ced9793a 100644 --- a/integration/policy_test.go +++ b/integration/policy_test.go @@ -147,6 +147,38 @@ func TestSetRequestHeaders(t *testing.T) { } +func TestRemoveRequestHeaders(t *testing.T) { + ctx := mainCtx + ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) + defer clearTimeout() + + client := testcluster.NewHTTPClient() + + req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/", nil) + if err != nil { + t.Fatal(err) + } + req.Header.Add("X-Custom-Request-Header-To-Remove", "foo") + + res, err := client.Do(req) + if !assert.NoError(t, err, "unexpected http error") { + return + } + defer res.Body.Close() + + var result struct { + Headers map[string]string `json:"headers"` + } + err = json.NewDecoder(res.Body).Decode(&result) + if !assert.NoError(t, err) { + return + } + + _, exist := result.Headers["X-Custom-Request-Header-To-Remove"] + assert.False(t, exist, "expected X-Custom-Request-Header-To-Remove not to be present.") + +} + func TestWebsocket(t *testing.T) { ctx := mainCtx ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) diff --git a/internal/controlplane/xds_routes.go b/internal/controlplane/xds_routes.go index ac4c3464d..68f461704 100644 --- a/internal/controlplane/xds_routes.go +++ b/internal/controlplane/xds_routes.go @@ -182,7 +182,8 @@ func buildPolicyRoutes(options *config.Options, domain string) []*envoy_config_r Timeout: routeTimeout, }, }, - RequestHeadersToAdd: requestHeadersToAdd, + RequestHeadersToAdd: requestHeadersToAdd, + RequestHeadersToRemove: policy.RemoveRequestHeaders, }) } return routes diff --git a/internal/controlplane/xds_routes_test.go b/internal/controlplane/xds_routes_test.go index c872205e6..bc3563b01 100644 --- a/internal/controlplane/xds_routes_test.go +++ b/internal/controlplane/xds_routes_test.go @@ -221,8 +221,15 @@ func Test_buildPolicyRoutes(t *testing.T) { Source: &config.StringURL{URL: mustParseURL("https://example.com")}, Regex: `^/[a]+$`, }, + { + Source: &config.StringURL{URL: mustParseURL("https://example.com")}, + Prefix: "/some/prefix/", + RemoveRequestHeaders: []string{"HEADER-KEY"}, + UpstreamTimeout: time.Minute, + }, }, }, "example.com") + testutil.AssertProtoJSONEqual(t, ` [ { @@ -240,7 +247,7 @@ func Test_buildPolicyRoutes(t *testing.T) { }, "route": { "autoHostRewrite": true, - "cluster": "policy-d00072a199d7b614", + "cluster": "policy-4e2763e591b22dc8", "timeout": "3s", "upgradeConfigs": [{ "enabled": false, @@ -263,7 +270,7 @@ func Test_buildPolicyRoutes(t *testing.T) { }, "route": { "autoHostRewrite": false, - "cluster": "policy-907a31075a413547", + "cluster": "policy-e5d20435224ae9b", "timeout": "0s", "upgradeConfigs": [{ "enabled": true, @@ -286,7 +293,7 @@ func Test_buildPolicyRoutes(t *testing.T) { }, "route": { "autoHostRewrite": true, - "cluster": "policy-f05528f790686bc3", + "cluster": "policy-6e7239b3980df01f", "timeout": "60s", "upgradeConfigs": [{ "enabled": false, @@ -319,13 +326,37 @@ func Test_buildPolicyRoutes(t *testing.T) { }, "route": { "autoHostRewrite": true, - "cluster": "policy-e5d3a05ff1f97659", + "cluster": "policy-7bf4b11bf99ced85", "timeout": "3s", "upgradeConfigs": [{ "enabled": false, "upgradeType": "websocket" }] } + }, + { + "name": "policy-5", + "match": { + "prefix": "/some/prefix/" + }, + "metadata": { + "filterMetadata": { + "envoy.filters.http.lua": { + "remove_pomerium_authorization": true, + "remove_pomerium_cookie": "pomerium" + } + } + }, + "route": { + "autoHostRewrite": true, + "cluster": "policy-6b5e934ff586365d", + "timeout": "60s", + "upgradeConfigs": [{ + "enabled": false, + "upgradeType": "websocket" + }] + }, + "requestHeadersToRemove": ["HEADER-KEY"] } ] `, routes)