diff --git a/config/envoyconfig/http_connection_manager.go b/config/envoyconfig/http_connection_manager.go index 0fac5d3e5..16226545f 100644 --- a/config/envoyconfig/http_connection_manager.go +++ b/config/envoyconfig/http_connection_manager.go @@ -21,17 +21,12 @@ func (b *Builder) buildVirtualHost( } // these routes match /.pomerium/... and similar paths - rs, err := b.buildPomeriumHTTPRoutes(options, host) + rs, err := b.buildPomeriumHTTPRoutes(options, host, requireStrictTransportSecurity) if err != nil { return nil, err } vh.Routes = append(vh.Routes, rs...) - // if we're the proxy or authenticate service, add our global headers - if config.IsProxy(options.Services) || config.IsAuthenticate(options.Services) { - vh.ResponseHeadersToAdd = toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)) - } - return vh, nil } diff --git a/config/envoyconfig/route_configurations.go b/config/envoyconfig/route_configurations.go index 0ab37eef3..ba2475ea4 100644 --- a/config/envoyconfig/route_configurations.go +++ b/config/envoyconfig/route_configurations.go @@ -78,7 +78,7 @@ func (b *Builder) buildMainRouteConfiguration( // if we're the proxy, add all the policy routes if config.IsProxy(cfg.Options.Services) { - rs, err := b.buildPolicyRoutes(cfg.Options, host) + rs, err := b.buildPolicyRoutes(cfg.Options, host, requireStrictTransportSecurity) if err != nil { return nil, err } diff --git a/config/envoyconfig/routes.go b/config/envoyconfig/routes.go index 881119cc1..3f0b93133 100644 --- a/config/envoyconfig/routes.go +++ b/config/envoyconfig/routes.go @@ -47,7 +47,11 @@ func (b *Builder) buildGRPCRoutes() ([]*envoy_config_route_v3.Route, error) { }}, nil } -func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, host string) ([]*envoy_config_route_v3.Route, error) { +func (b *Builder) buildPomeriumHTTPRoutes( + options *config.Options, + host string, + requireStrictTransportSecurity bool, +) ([]*envoy_config_route_v3.Route, error) { var routes []*envoy_config_route_v3.Route // if this is the pomerium proxy in front of the the authenticate service, don't add @@ -59,23 +63,23 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, host string) if !isFrontingAuthenticate { routes = append(routes, // enable ext_authz - b.buildControlPlanePathRoute("/.pomerium/jwt", true), - b.buildControlPlanePathRoute(urlutil.WebAuthnURLPath, true), + b.buildControlPlanePathRoute(options, "/.pomerium/jwt", true, requireStrictTransportSecurity), + b.buildControlPlanePathRoute(options, urlutil.WebAuthnURLPath, true, requireStrictTransportSecurity), // disable ext_authz and passthrough to proxy handlers - b.buildControlPlanePathRoute("/ping", false), - b.buildControlPlanePathRoute("/healthz", false), - b.buildControlPlanePathRoute("/.pomerium", false), - b.buildControlPlanePrefixRoute("/.pomerium/", false), - b.buildControlPlanePathRoute("/.well-known/pomerium", false), - b.buildControlPlanePrefixRoute("/.well-known/pomerium/", false), + b.buildControlPlanePathRoute(options, "/ping", false, requireStrictTransportSecurity), + b.buildControlPlanePathRoute(options, "/healthz", false, requireStrictTransportSecurity), + b.buildControlPlanePathRoute(options, "/.pomerium", false, requireStrictTransportSecurity), + b.buildControlPlanePrefixRoute(options, "/.pomerium/", false, requireStrictTransportSecurity), + b.buildControlPlanePathRoute(options, "/.well-known/pomerium", false, requireStrictTransportSecurity), + b.buildControlPlanePrefixRoute(options, "/.well-known/pomerium/", false, requireStrictTransportSecurity), ) // per #837, only add robots.txt if there are no unauthenticated routes if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: host, Path: "/robots.txt"}) { - routes = append(routes, b.buildControlPlanePathRoute("/robots.txt", false)) + routes = append(routes, b.buildControlPlanePathRoute(options, "/robots.txt", false, requireStrictTransportSecurity)) } } - authRoutes, err := b.buildPomeriumAuthenticateHTTPRoutes(options, host) + authRoutes, err := b.buildPomeriumAuthenticateHTTPRoutes(options, host, requireStrictTransportSecurity) if err != nil { return nil, err } @@ -83,7 +87,11 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, host string) return routes, nil } -func (b *Builder) buildPomeriumAuthenticateHTTPRoutes(options *config.Options, host string) ([]*envoy_config_route_v3.Route, error) { +func (b *Builder) buildPomeriumAuthenticateHTTPRoutes( + options *config.Options, + host string, + requireStrictTransportSecurity bool, +) ([]*envoy_config_route_v3.Route, error) { if !config.IsAuthenticate(options.Services) { return nil, nil } @@ -98,15 +106,20 @@ func (b *Builder) buildPomeriumAuthenticateHTTPRoutes(options *config.Options, h } if urlMatchesHost(u, host) { return []*envoy_config_route_v3.Route{ - b.buildControlPlanePathRoute(options.AuthenticateCallbackPath, false), - b.buildControlPlanePathRoute("/", false), + b.buildControlPlanePathRoute(options, options.AuthenticateCallbackPath, false, requireStrictTransportSecurity), + b.buildControlPlanePathRoute(options, "/", false, requireStrictTransportSecurity), }, nil } } return nil, nil } -func (b *Builder) buildControlPlanePathRoute(path string, protected bool) *envoy_config_route_v3.Route { +func (b *Builder) buildControlPlanePathRoute( + options *config.Options, + path string, + protected bool, + requireStrictTransportSecurity bool, +) *envoy_config_route_v3.Route { r := &envoy_config_route_v3.Route{ Name: "pomerium-path-" + path, Match: &envoy_config_route_v3.RouteMatch{ @@ -119,6 +132,7 @@ func (b *Builder) buildControlPlanePathRoute(path string, protected bool) *envoy }, }, }, + ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)), } if !protected { r.TypedPerFilterConfig = map[string]*any.Any{ @@ -128,7 +142,12 @@ func (b *Builder) buildControlPlanePathRoute(path string, protected bool) *envoy return r } -func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) *envoy_config_route_v3.Route { +func (b *Builder) buildControlPlanePrefixRoute( + options *config.Options, + prefix string, + protected bool, + requireStrictTransportSecurity bool, +) *envoy_config_route_v3.Route { r := &envoy_config_route_v3.Route{ Name: "pomerium-prefix-" + prefix, Match: &envoy_config_route_v3.RouteMatch{ @@ -141,6 +160,7 @@ func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) *e }, }, }, + ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)), } if !protected { r.TypedPerFilterConfig = map[string]*any.Any{ @@ -169,7 +189,11 @@ func getClusterStatsName(policy *config.Policy) string { return "" } -func (b *Builder) buildPolicyRoutes(options *config.Options, host string) ([]*envoy_config_route_v3.Route, error) { +func (b *Builder) buildPolicyRoutes( + options *config.Options, + host string, + requireStrictTransportSecurity bool, +) ([]*envoy_config_route_v3.Route, error) { var routes []*envoy_config_route_v3.Route for i, p := range options.GetAllPolicies() { @@ -185,7 +209,7 @@ func (b *Builder) buildPolicyRoutes(options *config.Options, host string) ([]*en Metadata: &envoy_config_core_v3.Metadata{}, RequestHeadersToAdd: toEnvoyHeaders(policy.SetRequestHeaders), RequestHeadersToRemove: getRequestHeadersToRemove(options, &policy), - ResponseHeadersToAdd: toEnvoyHeaders(policy.SetResponseHeaders), + ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeadersForPolicy(&policy, requireStrictTransportSecurity)), } if policy.Redirect != nil { action, err := b.buildPolicyRouteRedirectAction(policy.Redirect) diff --git a/config/envoyconfig/routes_test.go b/config/envoyconfig/routes_test.go index 1916e13fa..6046e5ef0 100644 --- a/config/envoyconfig/routes_test.go +++ b/config/envoyconfig/routes_test.go @@ -61,6 +61,22 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { "match": { "` + typ + `": "` + name + `" }, + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } + ], "route": { "cluster": "pomerium-control-plane-http" } @@ -84,7 +100,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { AuthenticateURLString: "https://authenticate.example.com", AuthenticateCallbackPath: "/oauth2/callback", } - routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com") + routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, `[ @@ -107,7 +123,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { AuthenticateURLString: "https://authenticate.example.com", AuthenticateCallbackPath: "/oauth2/callback", } - routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com") + routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, "null", routes) }) @@ -123,7 +139,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { }}, } _ = options.Policies[0].Validate() - routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com") + routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, `[ @@ -151,7 +167,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { }}, } _ = options.Policies[0].Validate() - routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com") + routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, `[ @@ -168,14 +184,31 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { } func Test_buildControlPlanePathRoute(t *testing.T) { + options := config.NewDefaultOptions() b := &Builder{filemgr: filemgr.NewManager()} - route := b.buildControlPlanePathRoute("/hello/world", false) + route := b.buildControlPlanePathRoute(options, "/hello/world", false, false) testutil.AssertProtoJSONEqual(t, ` { "name": "pomerium-path-/hello/world", "match": { "path": "/hello/world" }, + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } + ], "route": { "cluster": "pomerium-control-plane-http" }, @@ -190,14 +223,31 @@ func Test_buildControlPlanePathRoute(t *testing.T) { } func Test_buildControlPlanePrefixRoute(t *testing.T) { + options := config.NewDefaultOptions() b := &Builder{filemgr: filemgr.NewManager()} - route := b.buildControlPlanePrefixRoute("/hello/world/", false) + route := b.buildControlPlanePrefixRoute(options, "/hello/world/", false, false) testutil.AssertProtoJSONEqual(t, ` { "name": "pomerium-prefix-/hello/world/", "match": { "prefix": "/hello/world/" }, + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } + ], "route": { "cluster": "pomerium-control-plane-http" }, @@ -255,7 +305,7 @@ func TestTimeouts(t *testing.T) { AllowWebsockets: tc.allowWebsockets, }, }, - }, "example.com") + }, "example.com", false) if !assert.NoError(t, err, "%v", tc) || !assert.Len(t, routes, 1, tc) || !assert.NotNil(t, routes[0].GetRoute(), "%v", tc) { continue } @@ -359,7 +409,7 @@ func Test_buildPolicyRoutes(t *testing.T) { UpstreamTimeout: &ten, }, }, - }, "example.com") + }, "example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -405,6 +455,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -449,6 +515,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -499,6 +581,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -545,6 +643,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -589,6 +703,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "HEADER-KEY", "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -632,6 +762,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -676,6 +822,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -720,6 +882,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] } ] @@ -737,7 +915,7 @@ func Test_buildPolicyRoutes(t *testing.T) { PassIdentityHeaders: true, }, }, - }, "authenticate.example.com") + }, "authenticate.example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -781,6 +959,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } + ], "typedPerFilterConfig": { "envoy.filters.http.ext_authz": { "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", @@ -807,7 +1001,7 @@ func Test_buildPolicyRoutes(t *testing.T) { UpstreamTimeout: &ten, }, }, - }, "example.com:22") + }, "example.com:22", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -855,6 +1049,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -900,6 +1110,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] } ] @@ -920,7 +1146,7 @@ func Test_buildPolicyRoutes(t *testing.T) { Source: &config.StringURL{URL: mustParseURL(t, "https://from.example.com")}, }, }, - }, "from.example.com") + }, "from.example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -969,6 +1195,22 @@ func Test_buildPolicyRoutes(t *testing.T) { "x-email", "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] } ] @@ -1024,7 +1266,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { HostPathRegexRewriteSubstitution: "\\1", }, }, - }, "example.com") + }, "example.com", false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -1071,6 +1313,22 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -1115,6 +1373,22 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -1165,6 +1439,22 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -1209,6 +1499,22 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -1253,6 +1559,22 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] }, { @@ -1303,6 +1625,22 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { "requestHeadersToRemove": [ "x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy-hmac" + ], + "responseHeadersToAdd": [ + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + } + }, + { + "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "X-XSS-Protection", + "value": "1; mode=block" + } + } ] } ] diff --git a/config/options.go b/config/options.go index 8e38a861a..0f416af79 100644 --- a/config/options.go +++ b/config/options.go @@ -1009,6 +1009,11 @@ func (o *Options) GetGoogleCloudServerlessAuthenticationServiceAccount() string // GetSetResponseHeaders gets the SetResponseHeaders. func (o *Options) GetSetResponseHeaders(requireStrictTransportSecurity bool) map[string]string { + return o.GetSetResponseHeadersForPolicy(nil, requireStrictTransportSecurity) +} + +// GetSetResponseHeadersForPolicy gets the SetResponseHeaders for a policy. +func (o *Options) GetSetResponseHeadersForPolicy(policy *Policy, requireStrictTransportSecurity bool) map[string]string { hdrs := o.SetResponseHeaders if hdrs == nil { hdrs = make(map[string]string) @@ -1016,12 +1021,23 @@ func (o *Options) GetSetResponseHeaders(requireStrictTransportSecurity bool) map hdrs[k] = v } } - if _, ok := o.SetResponseHeaders[DisableHeaderKey]; ok { + if _, ok := hdrs[DisableHeaderKey]; ok { hdrs = make(map[string]string) } + + if policy != nil && policy.SetResponseHeaders != nil { + for k, v := range policy.SetResponseHeaders { + hdrs[k] = v + } + } + if _, ok := hdrs[DisableHeaderKey]; ok { + hdrs = make(map[string]string) + } + if !requireStrictTransportSecurity { delete(hdrs, "Strict-Transport-Security") } + return hdrs } diff --git a/config/options_test.go b/config/options_test.go index 70b627d60..e8a76cc18 100644 --- a/config/options_test.go +++ b/config/options_test.go @@ -766,6 +766,17 @@ func TestOptions_GetSetResponseHeaders(t *testing.T) { }) } +func TestOptions_GetSetResponseHeadersForPolicy(t *testing.T) { + t.Run("disable but set in policy", func(t *testing.T) { + options := NewDefaultOptions() + options.SetResponseHeaders = map[string]string{DisableHeaderKey: "1"} + policy := &Policy{ + SetResponseHeaders: map[string]string{"x": "y"}, + } + assert.Equal(t, map[string]string{"x": "y"}, options.GetSetResponseHeadersForPolicy(policy, true)) + }) +} + func TestOptions_GetSharedKey(t *testing.T) { t.Run("default", func(t *testing.T) { o := NewDefaultOptions()