diff --git a/config/envoyconfig/http_connection_manager.go b/config/envoyconfig/http_connection_manager.go index 16226545f..1b2531c92 100644 --- a/config/envoyconfig/http_connection_manager.go +++ b/config/envoyconfig/http_connection_manager.go @@ -13,7 +13,6 @@ func (b *Builder) buildVirtualHost( options *config.Options, name string, host string, - requireStrictTransportSecurity bool, ) (*envoy_config_route_v3.VirtualHost, error) { vh := &envoy_config_route_v3.VirtualHost{ Name: name, @@ -21,7 +20,7 @@ func (b *Builder) buildVirtualHost( } // these routes match /.pomerium/... and similar paths - rs, err := b.buildPomeriumHTTPRoutes(options, host, requireStrictTransportSecurity) + rs, err := b.buildPomeriumHTTPRoutes(options, host) if err != nil { return nil, err } @@ -34,13 +33,12 @@ func (b *Builder) buildVirtualHost( // coming directly from envoy func (b *Builder) buildLocalReplyConfig( options *config.Options, - requireStrictTransportSecurity bool, ) *envoy_http_connection_manager.LocalReplyConfig { // add global headers for HSTS headers (#2110) var headers []*envoy_config_core_v3.HeaderValueOption // if we're the proxy or authenticate service, add our global headers if config.IsProxy(options.Services) || config.IsAuthenticate(options.Services) { - headers = toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)) + headers = toEnvoyHeaders(options.GetSetResponseHeaders()) } return &envoy_http_connection_manager.LocalReplyConfig{ diff --git a/config/envoyconfig/listeners.go b/config/envoyconfig/listeners.go index b8c5005e9..49c257114 100644 --- a/config/envoyconfig/listeners.go +++ b/config/envoyconfig/listeners.go @@ -298,7 +298,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter( UseRemoteAddress: &wrappers.BoolValue{Value: true}, SkipXffAppend: cfg.Options.SkipXffAppend, XffNumTrustedHops: cfg.Options.XffNumTrustedHops, - LocalReplyConfig: b.buildLocalReplyConfig(cfg.Options, false), + LocalReplyConfig: b.buildLocalReplyConfig(cfg.Options), NormalizePath: wrapperspb.Bool(true), } diff --git a/config/envoyconfig/route_configurations.go b/config/envoyconfig/route_configurations.go index 332ee2914..28da5f464 100644 --- a/config/envoyconfig/route_configurations.go +++ b/config/envoyconfig/route_configurations.go @@ -2,12 +2,10 @@ package envoyconfig import ( "context" - "crypto/tls" envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" "github.com/pomerium/pomerium/config" - "github.com/pomerium/pomerium/pkg/cryptutil" ) // BuildRouteConfigurations builds the route configurations for the RDS service. @@ -32,15 +30,6 @@ func (b *Builder) buildMainRouteConfiguration( _ context.Context, cfg *config.Config, ) (*envoy_config_route_v3.RouteConfiguration, error) { - var certs []tls.Certificate - if !cfg.Options.InsecureServer { - var err error - certs, err = getAllCertificates(cfg) - if err != nil { - return nil, err - } - } - authorizeURLs, err := cfg.Options.GetInternalAuthorizeURLs() if err != nil { return nil, err @@ -58,8 +47,7 @@ func (b *Builder) buildMainRouteConfiguration( var virtualHosts []*envoy_config_route_v3.VirtualHost for _, host := range allHosts { - requireStrictTransportSecurity := cryptutil.HasCertificateForServerName(certs, host) - vh, err := b.buildVirtualHost(cfg.Options, host, host, requireStrictTransportSecurity) + vh, err := b.buildVirtualHost(cfg.Options, host, host) if err != nil { return nil, err } @@ -78,7 +66,7 @@ func (b *Builder) buildMainRouteConfiguration( // if we're the proxy, add all the policy routes if config.IsProxy(cfg.Options.Services) { - rs, err := b.buildRoutesForPoliciesWithHost(cfg, certs, host) + rs, err := b.buildRoutesForPoliciesWithHost(cfg, host) if err != nil { return nil, err } @@ -90,12 +78,12 @@ func (b *Builder) buildMainRouteConfiguration( } } - vh, err := b.buildVirtualHost(cfg.Options, "catch-all", "*", false) + vh, err := b.buildVirtualHost(cfg.Options, "catch-all", "*") if err != nil { return nil, err } if config.IsProxy(cfg.Options.Services) { - rs, err := b.buildRoutesForPoliciesWithCatchAll(cfg, certs) + rs, err := b.buildRoutesForPoliciesWithCatchAll(cfg) if err != nil { return nil, err } diff --git a/config/envoyconfig/route_configurations_test.go b/config/envoyconfig/route_configurations_test.go index b0b2f475b..d59521403 100644 --- a/config/envoyconfig/route_configurations_test.go +++ b/config/envoyconfig/route_configurations_test.go @@ -41,13 +41,13 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) { "name": "catch-all", "domains": ["*"], "routes": [ - `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/ping", false))+`, - `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/healthz", false))+`, - `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium", false))+`, - `+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.pomerium/", false))+`, - `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.well-known/pomerium", false))+`, - `+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.well-known/pomerium/", false))+`, - `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/robots.txt", false))+`, + `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/ping"))+`, + `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/healthz"))+`, + `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium"))+`, + `+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.pomerium/"))+`, + `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.well-known/pomerium"))+`, + `+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.well-known/pomerium/"))+`, + `+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/robots.txt"))+`, { "name": "policy-0", "match": { diff --git a/config/envoyconfig/routes.go b/config/envoyconfig/routes.go index 28a1c7013..555dbff7f 100644 --- a/config/envoyconfig/routes.go +++ b/config/envoyconfig/routes.go @@ -1,7 +1,6 @@ package envoyconfig import ( - "crypto/tls" "encoding/json" "fmt" "net/url" @@ -20,7 +19,6 @@ import ( "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/urlutil" - "github.com/pomerium/pomerium/pkg/cryptutil" ) const ( @@ -53,7 +51,6 @@ func (b *Builder) buildGRPCRoutes() ([]*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 @@ -65,20 +62,20 @@ func (b *Builder) buildPomeriumHTTPRoutes( } if !isFrontingAuthenticate { routes = append(routes, - b.buildControlPlanePathRoute(options, "/ping", requireStrictTransportSecurity), - b.buildControlPlanePathRoute(options, "/healthz", requireStrictTransportSecurity), - b.buildControlPlanePathRoute(options, "/.pomerium", requireStrictTransportSecurity), - b.buildControlPlanePrefixRoute(options, "/.pomerium/", requireStrictTransportSecurity), - b.buildControlPlanePathRoute(options, "/.well-known/pomerium", requireStrictTransportSecurity), - b.buildControlPlanePrefixRoute(options, "/.well-known/pomerium/", requireStrictTransportSecurity), + b.buildControlPlanePathRoute(options, "/ping"), + b.buildControlPlanePathRoute(options, "/healthz"), + b.buildControlPlanePathRoute(options, "/.pomerium"), + b.buildControlPlanePrefixRoute(options, "/.pomerium/"), + b.buildControlPlanePathRoute(options, "/.well-known/pomerium"), + b.buildControlPlanePrefixRoute(options, "/.well-known/pomerium/"), ) // 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(options, "/robots.txt", requireStrictTransportSecurity)) + routes = append(routes, b.buildControlPlanePathRoute(options, "/robots.txt")) } } - authRoutes, err := b.buildPomeriumAuthenticateHTTPRoutes(options, host, requireStrictTransportSecurity) + authRoutes, err := b.buildPomeriumAuthenticateHTTPRoutes(options, host) if err != nil { return nil, err } @@ -89,7 +86,6 @@ func (b *Builder) buildPomeriumHTTPRoutes( 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 @@ -105,8 +101,8 @@ func (b *Builder) buildPomeriumAuthenticateHTTPRoutes( } if urlMatchesHost(u, host) { return []*envoy_config_route_v3.Route{ - b.buildControlPlanePathRoute(options, options.AuthenticateCallbackPath, requireStrictTransportSecurity), - b.buildControlPlanePathRoute(options, "/", requireStrictTransportSecurity), + b.buildControlPlanePathRoute(options, options.AuthenticateCallbackPath), + b.buildControlPlanePathRoute(options, "/"), }, nil } } @@ -116,7 +112,6 @@ func (b *Builder) buildPomeriumAuthenticateHTTPRoutes( func (b *Builder) buildControlPlanePathRoute( options *config.Options, path string, - requireStrictTransportSecurity bool, ) *envoy_config_route_v3.Route { r := &envoy_config_route_v3.Route{ Name: "pomerium-path-" + path, @@ -130,7 +125,7 @@ func (b *Builder) buildControlPlanePathRoute( }, }, }, - ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)), + ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders()), TypedPerFilterConfig: map[string]*any.Any{ PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzContextExtensions(MakeExtAuthzContextExtensions(true, 0)), }, @@ -141,7 +136,6 @@ func (b *Builder) buildControlPlanePathRoute( func (b *Builder) buildControlPlanePrefixRoute( options *config.Options, prefix string, - requireStrictTransportSecurity bool, ) *envoy_config_route_v3.Route { r := &envoy_config_route_v3.Route{ Name: "pomerium-prefix-" + prefix, @@ -155,7 +149,7 @@ func (b *Builder) buildControlPlanePrefixRoute( }, }, }, - ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)), + ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders()), TypedPerFilterConfig: map[string]*any.Any{ PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzContextExtensions(MakeExtAuthzContextExtensions(true, 0)), }, @@ -184,7 +178,6 @@ func getClusterStatsName(policy *config.Policy) string { func (b *Builder) buildRoutesForPoliciesWithHost( cfg *config.Config, - certs []tls.Certificate, host string, ) ([]*envoy_config_route_v3.Route, error) { var routes []*envoy_config_route_v3.Route @@ -199,7 +192,7 @@ func (b *Builder) buildRoutesForPoliciesWithHost( continue } - policyRoutes, err := b.buildRoutesForPolicy(cfg, certs, &policy, fmt.Sprintf("policy-%d", i)) + policyRoutes, err := b.buildRoutesForPolicy(cfg, &policy, fmt.Sprintf("policy-%d", i)) if err != nil { return nil, err } @@ -211,7 +204,6 @@ func (b *Builder) buildRoutesForPoliciesWithHost( func (b *Builder) buildRoutesForPoliciesWithCatchAll( cfg *config.Config, - certs []tls.Certificate, ) ([]*envoy_config_route_v3.Route, error) { var routes []*envoy_config_route_v3.Route for i, p := range cfg.Options.GetAllPolicies() { @@ -225,7 +217,7 @@ func (b *Builder) buildRoutesForPoliciesWithCatchAll( continue } - policyRoutes, err := b.buildRoutesForPolicy(cfg, certs, &policy, fmt.Sprintf("policy-%d", i)) + policyRoutes, err := b.buildRoutesForPolicy(cfg, &policy, fmt.Sprintf("policy-%d", i)) if err != nil { return nil, err } @@ -237,7 +229,6 @@ func (b *Builder) buildRoutesForPoliciesWithCatchAll( func (b *Builder) buildRoutesForPolicy( cfg *config.Config, - certs []tls.Certificate, policy *config.Policy, name string, ) ([]*envoy_config_route_v3.Route, error) { @@ -250,14 +241,14 @@ func (b *Builder) buildRoutesForPolicy( if strings.Contains(fromURL.Host, "*") { // we have to match '*.example.com' and '*.example.com:443', so there are two routes for _, host := range urlutil.GetDomainsForURL(fromURL) { - route, err := b.buildRouteForPolicyAndMatch(cfg, certs, policy, name, mkRouteMatchForHost(policy, host)) + route, err := b.buildRouteForPolicyAndMatch(cfg, policy, name, mkRouteMatchForHost(policy, host)) if err != nil { return nil, err } routes = append(routes, route) } } else { - route, err := b.buildRouteForPolicyAndMatch(cfg, certs, policy, name, mkRouteMatch(policy)) + route, err := b.buildRouteForPolicyAndMatch(cfg, policy, name, mkRouteMatch(policy)) if err != nil { return nil, err } @@ -268,7 +259,6 @@ func (b *Builder) buildRoutesForPolicy( func (b *Builder) buildRouteForPolicyAndMatch( cfg *config.Config, - certs []tls.Certificate, policy *config.Policy, name string, match *envoy_config_route_v3.RouteMatch, @@ -283,15 +273,13 @@ func (b *Builder) buildRouteForPolicyAndMatch( return nil, err } - requireStrictTransportSecurity := cryptutil.HasCertificateForServerName(certs, fromURL.Hostname()) - route := &envoy_config_route_v3.Route{ Name: name, Match: match, Metadata: &envoy_config_core_v3.Metadata{}, RequestHeadersToAdd: toEnvoyHeaders(policy.SetRequestHeaders), RequestHeadersToRemove: getRequestHeadersToRemove(cfg.Options, policy), - ResponseHeadersToAdd: toEnvoyHeaders(cfg.Options.GetSetResponseHeadersForPolicy(policy, requireStrictTransportSecurity)), + ResponseHeadersToAdd: toEnvoyHeaders(cfg.Options.GetSetResponseHeadersForPolicy(policy)), } 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 28e68bce9..b59f2fb76 100644 --- a/config/envoyconfig/routes_test.go +++ b/config/envoyconfig/routes_test.go @@ -100,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", false) + routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, `[ @@ -121,7 +121,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { AuthenticateURLString: "https://authenticate.example.com", AuthenticateCallbackPath: "/oauth2/callback", } - routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com", false) + routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, "null", routes) }) @@ -137,7 +137,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { }}, } _ = options.Policies[0].Validate() - routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com", false) + routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, `[ @@ -163,7 +163,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { }}, } _ = options.Policies[0].Validate() - routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com", false) + routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, `[ @@ -180,7 +180,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { func Test_buildControlPlanePathRoute(t *testing.T) { options := config.NewDefaultOptions() b := &Builder{filemgr: filemgr.NewManager()} - route := b.buildControlPlanePathRoute(options, "/hello/world", false) + route := b.buildControlPlanePathRoute(options, "/hello/world") testutil.AssertProtoJSONEqual(t, ` { "name": "pomerium-path-/hello/world", @@ -224,7 +224,7 @@ func Test_buildControlPlanePathRoute(t *testing.T) { func Test_buildControlPlanePrefixRoute(t *testing.T) { options := config.NewDefaultOptions() b := &Builder{filemgr: filemgr.NewManager()} - route := b.buildControlPlanePrefixRoute(options, "/hello/world/", false) + route := b.buildControlPlanePrefixRoute(options, "/hello/world/") testutil.AssertProtoJSONEqual(t, ` { "name": "pomerium-prefix-/hello/world/", @@ -311,7 +311,7 @@ func TestTimeouts(t *testing.T) { AllowWebsockets: tc.allowWebsockets, }, }, - }}, nil, "example.com") + }}, "example.com") if !assert.NoError(t, err, "%v", tc) || !assert.Len(t, routes, 1, tc) || !assert.NotNil(t, routes[0].GetRoute(), "%v", tc) { continue } @@ -425,7 +425,7 @@ func Test_buildPolicyRoutes(t *testing.T) { UpstreamTimeout: &ten, }, }, - }}, nil, "example.com") + }}, "example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -1020,7 +1020,7 @@ func Test_buildPolicyRoutes(t *testing.T) { PassIdentityHeaders: true, }, }, - }}, nil, "authenticate.example.com") + }}, "authenticate.example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -1109,7 +1109,7 @@ func Test_buildPolicyRoutes(t *testing.T) { UpstreamTimeout: &ten, }, }, - }}, nil, "example.com:22") + }}, "example.com:22") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -1278,7 +1278,7 @@ func Test_buildPolicyRoutes(t *testing.T) { To: mustParseWeightedURLs(t, "https://to.example.com"), }, }, - }}, nil, "from.example.com") + }}, "from.example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` @@ -1410,7 +1410,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) { HostPathRegexRewriteSubstitution: "\\1", }, }, - }}, nil, "example.com") + }}, "example.com") require.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` diff --git a/config/options.go b/config/options.go index 6df5af5e0..388c4aff4 100644 --- a/config/options.go +++ b/config/options.go @@ -978,6 +978,11 @@ func (o *Options) GetCertificates() ([]tls.Certificate, error) { return certs, nil } +// HasCertificates returns true if options has any certificates. +func (o *Options) HasCertificates() bool { + return o.Cert != "" || o.Key != "" || len(o.CertificateFiles) > 0 || o.CertFile != "" || o.KeyFile != "" +} + // GetSharedKey gets the decoded shared key. func (o *Options) GetSharedKey() ([]byte, error) { sharedKey := o.SharedKey @@ -1017,18 +1022,22 @@ func (o *Options) GetGoogleCloudServerlessAuthenticationServiceAccount() string } // GetSetResponseHeaders gets the SetResponseHeaders. -func (o *Options) GetSetResponseHeaders(requireStrictTransportSecurity bool) map[string]string { - return o.GetSetResponseHeadersForPolicy(nil, requireStrictTransportSecurity) +func (o *Options) GetSetResponseHeaders() map[string]string { + return o.GetSetResponseHeadersForPolicy(nil) } // GetSetResponseHeadersForPolicy gets the SetResponseHeaders for a policy. -func (o *Options) GetSetResponseHeadersForPolicy(policy *Policy, requireStrictTransportSecurity bool) map[string]string { +func (o *Options) GetSetResponseHeadersForPolicy(policy *Policy) map[string]string { hdrs := o.SetResponseHeaders if hdrs == nil { hdrs = make(map[string]string) for k, v := range defaultSetResponseHeaders { hdrs[k] = v } + + if !o.HasCertificates() { + delete(hdrs, "Strict-Transport-Security") + } } if _, ok := hdrs[DisableHeaderKey]; ok { hdrs = make(map[string]string) @@ -1043,10 +1052,6 @@ func (o *Options) GetSetResponseHeadersForPolicy(policy *Policy, requireStrictTr 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 7a1cdaa3a..42ab36d59 100644 --- a/config/options_test.go +++ b/config/options_test.go @@ -752,20 +752,21 @@ func TestOptions_GetSetResponseHeaders(t *testing.T) { assert.Equal(t, map[string]string{ "X-Frame-Options": "SAMEORIGIN", "X-XSS-Protection": "1; mode=block", - }, options.GetSetResponseHeaders(false)) + }, options.GetSetResponseHeaders()) }) t.Run("strict", func(t *testing.T) { options := NewDefaultOptions() + options.Cert = "CERT" assert.Equal(t, map[string]string{ "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", "X-Frame-Options": "SAMEORIGIN", "X-XSS-Protection": "1; mode=block", - }, options.GetSetResponseHeaders(true)) + }, options.GetSetResponseHeaders()) }) t.Run("disable", func(t *testing.T) { options := NewDefaultOptions() options.SetResponseHeaders = map[string]string{DisableHeaderKey: "1", "x-other": "xyz"} - assert.Equal(t, map[string]string{}, options.GetSetResponseHeaders(true)) + assert.Equal(t, map[string]string{}, options.GetSetResponseHeaders()) }) } @@ -776,7 +777,7 @@ func TestOptions_GetSetResponseHeadersForPolicy(t *testing.T) { policy := &Policy{ SetResponseHeaders: map[string]string{"x": "y"}, } - assert.Equal(t, map[string]string{"x": "y"}, options.GetSetResponseHeadersForPolicy(policy, true)) + assert.Equal(t, map[string]string{"x": "y"}, options.GetSetResponseHeadersForPolicy(policy)) }) }