diff --git a/config/envoyconfig/bootstrap.go b/config/envoyconfig/bootstrap.go index 9166d52fa..433115d56 100644 --- a/config/envoyconfig/bootstrap.go +++ b/config/envoyconfig/bootstrap.go @@ -1,6 +1,7 @@ package envoyconfig import ( + "context" "fmt" "os" "path/filepath" @@ -25,6 +26,47 @@ var ( envoyAdminClusterName = "pomerium-envoy-admin" ) +// BuildBootstrap builds the bootstrap config. +func (b *Builder) BuildBootstrap( + ctx context.Context, + cfg *config.Config, + fullyStatic bool, +) (bootstrap *envoy_config_bootstrap_v3.Bootstrap, err error) { + bootstrap = new(envoy_config_bootstrap_v3.Bootstrap) + + bootstrap.Admin, err = b.BuildBootstrapAdmin(cfg) + if err != nil { + return nil, fmt.Errorf("error building bootstrap admin: %w", err) + } + + bootstrap.DynamicResources, err = b.BuildBootstrapDynamicResources(cfg, fullyStatic) + if err != nil { + return nil, fmt.Errorf("error building bootstrap dynamic resources: %w", err) + } + + bootstrap.LayeredRuntime, err = b.BuildBootstrapLayeredRuntime() + if err != nil { + return nil, fmt.Errorf("error building bootstrap layered runtime: %w", err) + } + + bootstrap.Node = &envoy_config_core_v3.Node{ + Id: telemetry.ServiceName(cfg.Options.Services), + Cluster: telemetry.ServiceName(cfg.Options.Services), + } + + bootstrap.StaticResources, err = b.BuildBootstrapStaticResources(ctx, cfg, fullyStatic) + if err != nil { + return nil, fmt.Errorf("error building bootstrap static resources: %w", err) + } + + bootstrap.StatsConfig, err = b.BuildBootstrapStatsConfig(cfg) + if err != nil { + return nil, err + } + + return bootstrap, nil +} + // BuildBootstrapAdmin builds the admin config for the envoy bootstrap. func (b *Builder) BuildBootstrapAdmin(cfg *config.Config) (admin *envoy_config_bootstrap_v3.Admin, err error) { admin = &envoy_config_bootstrap_v3.Admin{ @@ -53,6 +95,39 @@ func (b *Builder) BuildBootstrapAdmin(cfg *config.Config) (admin *envoy_config_b return admin, nil } +// BuildBootstrapDynamicResources builds the dynamic resources for the envoy bootstrap. +func (b *Builder) BuildBootstrapDynamicResources( + cfg *config.Config, + fullyStatic bool, +) (dynamicResources *envoy_config_bootstrap_v3.Bootstrap_DynamicResources, err error) { + if fullyStatic { + return nil, nil + } + return &envoy_config_bootstrap_v3.Bootstrap_DynamicResources{ + AdsConfig: &envoy_config_core_v3.ApiConfigSource{ + ApiType: envoy_config_core_v3.ApiConfigSource_ApiType(envoy_config_core_v3.ApiConfigSource_ApiType_value["DELTA_GRPC"]), + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + GrpcServices: []*envoy_config_core_v3.GrpcService{ + { + TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{ + ClusterName: "pomerium-control-plane-grpc", + }, + }, + }, + }, + }, + LdsConfig: &envoy_config_core_v3.ConfigSource{ + ResourceApiVersion: envoy_config_core_v3.ApiVersion_V3, + ConfigSourceSpecifier: &envoy_config_core_v3.ConfigSource_Ads{}, + }, + CdsConfig: &envoy_config_core_v3.ConfigSource{ + ResourceApiVersion: envoy_config_core_v3.ApiVersion_V3, + ConfigSourceSpecifier: &envoy_config_core_v3.ConfigSource_Ads{}, + }, + }, nil +} + // BuildBootstrapLayeredRuntime builds the layered runtime for the envoy bootstrap. func (b *Builder) BuildBootstrapLayeredRuntime() (*envoy_config_bootstrap_v3.LayeredRuntime, error) { layer, err := structpb.NewStruct(map[string]interface{}{ @@ -78,7 +153,27 @@ func (b *Builder) BuildBootstrapLayeredRuntime() (*envoy_config_bootstrap_v3.Lay // BuildBootstrapStaticResources builds the static resources for the envoy bootstrap. It includes the control plane // cluster. -func (b *Builder) BuildBootstrapStaticResources() (*envoy_config_bootstrap_v3.Bootstrap_StaticResources, error) { +func (b *Builder) BuildBootstrapStaticResources( + ctx context.Context, + cfg *config.Config, + fullyStatic bool, +) (staticResources *envoy_config_bootstrap_v3.Bootstrap_StaticResources, err error) { + staticResources = new(envoy_config_bootstrap_v3.Bootstrap_StaticResources) + + if fullyStatic { + staticResources.Clusters, err = b.BuildClusters(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("error building clusters: %w", err) + } + + staticResources.Listeners, err = b.BuildListeners(ctx, cfg, fullyStatic) + if err != nil { + return nil, fmt.Errorf("error building listeners: %w", err) + } + + return staticResources, nil + } + grpcAddr, err := parseAddress(b.localGRPCAddress) if err != nil { return nil, fmt.Errorf("envoyconfig: invalid local gRPC address: %w", err) @@ -114,13 +209,9 @@ func (b *Builder) BuildBootstrapStaticResources() (*envoy_config_bootstrap_v3.Bo TypedExtensionProtocolOptions: buildTypedExtensionProtocolOptions(nil, upstreamProtocolHTTP2), } - staticCfg := &envoy_config_bootstrap_v3.Bootstrap_StaticResources{ - Clusters: []*envoy_config_cluster_v3.Cluster{ - controlPlaneCluster, - }, - } + staticResources.Clusters = append(staticResources.Clusters, controlPlaneCluster) - return staticCfg, nil + return staticResources, nil } // BuildBootstrapStatsConfig builds a the stats config the envoy bootstrap. diff --git a/config/envoyconfig/bootstrap_test.go b/config/envoyconfig/bootstrap_test.go index d80caad85..1823d85f7 100644 --- a/config/envoyconfig/bootstrap_test.go +++ b/config/envoyconfig/bootstrap_test.go @@ -1,6 +1,7 @@ package envoyconfig import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -51,7 +52,7 @@ func TestBuilder_BuildBootstrapLayeredRuntime(t *testing.T) { func TestBuilder_BuildBootstrapStaticResources(t *testing.T) { t.Run("valid", func(t *testing.T) { b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil) - staticCfg, err := b.BuildBootstrapStaticResources() + staticCfg, err := b.BuildBootstrapStaticResources(context.Background(), &config.Config{}, false) assert.NoError(t, err) testutil.AssertProtoJSONEqual(t, ` { @@ -95,7 +96,7 @@ func TestBuilder_BuildBootstrapStaticResources(t *testing.T) { }) t.Run("bad gRPC address", func(t *testing.T) { b := New("xyz:zyx", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil) - _, err := b.BuildBootstrapStaticResources() + _, err := b.BuildBootstrapStaticResources(context.Background(), &config.Config{}, false) assert.Error(t, err) }) } diff --git a/config/envoyconfig/listeners.go b/config/envoyconfig/listeners.go index 8bd935677..954802a88 100644 --- a/config/envoyconfig/listeners.go +++ b/config/envoyconfig/listeners.go @@ -56,11 +56,15 @@ func init() { } // BuildListeners builds envoy listeners from the given config. -func (b *Builder) BuildListeners(ctx context.Context, cfg *config.Config) ([]*envoy_config_listener_v3.Listener, error) { +func (b *Builder) BuildListeners( + ctx context.Context, + cfg *config.Config, + fullyStatic bool, +) ([]*envoy_config_listener_v3.Listener, error) { var listeners []*envoy_config_listener_v3.Listener if config.IsAuthenticate(cfg.Options.Services) || config.IsProxy(cfg.Options.Services) { - li, err := b.buildMainListener(ctx, cfg) + li, err := b.buildMainListener(ctx, cfg, fullyStatic) if err != nil { return nil, err } @@ -128,7 +132,11 @@ func (b *Builder) buildTLSSocket(ctx context.Context, cfg *config.Config, certs }, nil } -func (b *Builder) buildMainListener(ctx context.Context, cfg *config.Config) (*envoy_config_listener_v3.Listener, error) { +func (b *Builder) buildMainListener( + ctx context.Context, + cfg *config.Config, + fullyStatic bool, +) (*envoy_config_listener_v3.Listener, error) { li := newEnvoyListener("http-ingress") if cfg.Options.UseProxyProtocol { li.ListenerFilters = append(li.ListenerFilters, ProxyProtocolFilter()) @@ -137,7 +145,7 @@ func (b *Builder) buildMainListener(ctx context.Context, cfg *config.Config) (*e if cfg.Options.InsecureServer { li.Address = buildAddress(cfg.Options.Addr, 80) - filter, err := b.buildMainHTTPConnectionManagerFilter(cfg.Options) + filter, err := b.buildMainHTTPConnectionManagerFilter(ctx, cfg, fullyStatic) if err != nil { return nil, err } @@ -156,7 +164,7 @@ func (b *Builder) buildMainListener(ctx context.Context, cfg *config.Config) (*e return nil, err } - filter, err := b.buildMainHTTPConnectionManagerFilter(cfg.Options, allCertificates...) + filter, err := b.buildMainHTTPConnectionManagerFilter(ctx, cfg, fullyStatic) if err != nil { return nil, err } @@ -254,67 +262,13 @@ func (b *Builder) buildMetricsListener(cfg *config.Config) (*envoy_config_listen } func (b *Builder) buildMainHTTPConnectionManagerFilter( - options *config.Options, - certs ...tls.Certificate, + ctx context.Context, + cfg *config.Config, + fullyStatic bool, ) (*envoy_config_listener_v3.Filter, error) { - authorizeURLs, err := options.GetInternalAuthorizeURLs() - if err != nil { - return nil, err - } - - dataBrokerURLs, err := options.GetInternalDataBrokerURLs() - if err != nil { - return nil, err - } - - allHosts, err := getAllRouteableHosts(options, options.Addr) - if err != nil { - return nil, err - } - - var virtualHosts []*envoy_config_route_v3.VirtualHost - for _, host := range allHosts { - requireStrictTransportSecurity := cryptutil.HasCertificateForServerName(certs, host) - vh, err := b.buildVirtualHost(options, host, host, requireStrictTransportSecurity) - if err != nil { - return nil, err - } - - if options.Addr == options.GetGRPCAddr() { - // if this is a gRPC service domain and we're supposed to handle that, add those routes - if (config.IsAuthorize(options.Services) && urlsMatchHost(authorizeURLs, host)) || - (config.IsDataBroker(options.Services) && urlsMatchHost(dataBrokerURLs, host)) { - rs, err := b.buildGRPCRoutes() - if err != nil { - return nil, err - } - vh.Routes = append(vh.Routes, rs...) - } - } - - // if we're the proxy, add all the policy routes - if config.IsProxy(options.Services) { - rs, err := b.buildPolicyRoutes(options, host) - if err != nil { - return nil, err - } - vh.Routes = append(vh.Routes, rs...) - } - - if len(vh.Routes) > 0 { - virtualHosts = append(virtualHosts, vh) - } - } - - vh, err := b.buildVirtualHost(options, "catch-all", "*", false) - if err != nil { - return nil, err - } - virtualHosts = append(virtualHosts, vh) - var grpcClientTimeout *durationpb.Duration - if options.GRPCClientTimeout != 0 { - grpcClientTimeout = durationpb.New(options.GRPCClientTimeout) + if cfg.Options.GRPCClientTimeout != 0 { + grpcClientTimeout = durationpb.New(cfg.Options.GRPCClientTimeout) } else { grpcClientTimeout = durationpb.New(30 * time.Second) } @@ -329,45 +283,59 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter( filters = append(filters, HTTPRouterFilter()) var maxStreamDuration *durationpb.Duration - if options.WriteTimeout > 0 { - maxStreamDuration = durationpb.New(options.WriteTimeout) + if cfg.Options.WriteTimeout > 0 { + maxStreamDuration = durationpb.New(cfg.Options.WriteTimeout) } - rc, err := b.buildRouteConfiguration("main", virtualHosts) - if err != nil { - return nil, err - } - tracingProvider, err := buildTracingHTTP(options) + tracingProvider, err := buildTracingHTTP(cfg.Options) if err != nil { return nil, err } - return HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{ + mgr := &envoy_http_connection_manager.HttpConnectionManager{ AlwaysSetRequestIdInResponse: true, - - CodecType: options.GetCodecType().ToEnvoy(), - StatPrefix: "ingress", - RouteSpecifier: &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{ - RouteConfig: rc, - }, - HttpFilters: filters, - AccessLog: buildAccessLogs(options), + CodecType: cfg.Options.GetCodecType().ToEnvoy(), + StatPrefix: "ingress", + HttpFilters: filters, + AccessLog: buildAccessLogs(cfg.Options), CommonHttpProtocolOptions: &envoy_config_core_v3.HttpProtocolOptions{ - IdleTimeout: durationpb.New(options.IdleTimeout), + IdleTimeout: durationpb.New(cfg.Options.IdleTimeout), MaxStreamDuration: maxStreamDuration, }, HttpProtocolOptions: http1ProtocolOptions, - RequestTimeout: durationpb.New(options.ReadTimeout), + RequestTimeout: durationpb.New(cfg.Options.ReadTimeout), Tracing: &envoy_http_connection_manager.HttpConnectionManager_Tracing{ - RandomSampling: &envoy_type_v3.Percent{Value: options.TracingSampleRate * 100}, + RandomSampling: &envoy_type_v3.Percent{Value: cfg.Options.TracingSampleRate * 100}, Provider: tracingProvider, }, // See https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for UseRemoteAddress: &wrappers.BoolValue{Value: true}, - SkipXffAppend: options.SkipXffAppend, - XffNumTrustedHops: options.XffNumTrustedHops, - LocalReplyConfig: b.buildLocalReplyConfig(options, false), - }), nil + SkipXffAppend: cfg.Options.SkipXffAppend, + XffNumTrustedHops: cfg.Options.XffNumTrustedHops, + LocalReplyConfig: b.buildLocalReplyConfig(cfg.Options, false), + } + + if fullyStatic { + routeConfiguration, err := b.buildMainRouteConfiguration(ctx, cfg) + if err != nil { + return nil, err + } + mgr.RouteSpecifier = &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfiguration, + } + } else { + mgr.RouteSpecifier = &envoy_http_connection_manager.HttpConnectionManager_Rds{ + Rds: &envoy_http_connection_manager.Rds{ + ConfigSource: &envoy_config_core_v3.ConfigSource{ + ResourceApiVersion: envoy_config_core_v3.ApiVersion_V3, + ConfigSourceSpecifier: &envoy_config_core_v3.ConfigSource_Ads{}, + }, + RouteConfigName: "main", + }, + } + } + + return HTTPConnectionManagerFilter(mgr), nil } func (b *Builder) buildMetricsHTTPConnectionManagerFilter() (*envoy_config_listener_v3.Filter, error) { @@ -546,7 +514,8 @@ func (b *Builder) buildDownstreamTLSContextMulti( TlsCertificates: envoyCerts, AlpnProtocols: getALPNProtos(cfg.Options), ValidationContextType: b.buildDownstreamValidationContext(ctx, cfg), - }}, nil + }, + }, nil } func getALPNProtos(opts *config.Options) []string { diff --git a/config/envoyconfig/listeners_test.go b/config/envoyconfig/listeners_test.go index 3f67f0818..3bf1b3111 100644 --- a/config/envoyconfig/listeners_test.go +++ b/config/envoyconfig/listeners_test.go @@ -65,7 +65,7 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) { options.SkipXffAppend = true options.XffNumTrustedHops = 1 options.AuthenticateURLString = "https://authenticate.example.com" - filter, err := b.buildMainHTTPConnectionManagerFilter(options) + filter, err := b.buildMainHTTPConnectionManagerFilter(context.Background(), &config.Config{Options: options}, false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, testData(t, "main_http_connection_manager_filter.json", nil), filter) } @@ -353,7 +353,7 @@ func Test_requireProxyProtocol(t *testing.T) { li, err := b.buildMainListener(context.Background(), &config.Config{Options: &config.Options{ UseProxyProtocol: true, InsecureServer: true, - }}) + }}, false) require.NoError(t, err) testutil.AssertProtoJSONEqual(t, `[ { @@ -368,7 +368,7 @@ func Test_requireProxyProtocol(t *testing.T) { li, err := b.buildMainListener(context.Background(), &config.Config{Options: &config.Options{ UseProxyProtocol: false, InsecureServer: true, - }}) + }}, false) require.NoError(t, err) assert.Len(t, li.GetListenerFilters(), 0) }) diff --git a/config/envoyconfig/route_configurations.go b/config/envoyconfig/route_configurations.go new file mode 100644 index 000000000..0ab37eef3 --- /dev/null +++ b/config/envoyconfig/route_configurations.go @@ -0,0 +1,105 @@ +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. +func (b *Builder) BuildRouteConfigurations( + ctx context.Context, + cfg *config.Config, +) ([]*envoy_config_route_v3.RouteConfiguration, error) { + var routeConfigurations []*envoy_config_route_v3.RouteConfiguration + + if config.IsAuthenticate(cfg.Options.Services) || config.IsProxy(cfg.Options.Services) { + rc, err := b.buildMainRouteConfiguration(ctx, cfg) + if err != nil { + return nil, err + } + routeConfigurations = append(routeConfigurations, rc) + } + + return routeConfigurations, nil +} + +func (b *Builder) buildMainRouteConfiguration( + ctx 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 + } + + dataBrokerURLs, err := cfg.Options.GetInternalDataBrokerURLs() + if err != nil { + return nil, err + } + + allHosts, err := getAllRouteableHosts(cfg.Options, cfg.Options.Addr) + if err != nil { + return nil, err + } + + 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) + if err != nil { + return nil, err + } + + if cfg.Options.Addr == cfg.Options.GetGRPCAddr() { + // if this is a gRPC service domain and we're supposed to handle that, add those routes + if (config.IsAuthorize(cfg.Options.Services) && urlsMatchHost(authorizeURLs, host)) || + (config.IsDataBroker(cfg.Options.Services) && urlsMatchHost(dataBrokerURLs, host)) { + rs, err := b.buildGRPCRoutes() + if err != nil { + return nil, err + } + vh.Routes = append(vh.Routes, rs...) + } + } + + // if we're the proxy, add all the policy routes + if config.IsProxy(cfg.Options.Services) { + rs, err := b.buildPolicyRoutes(cfg.Options, host) + if err != nil { + return nil, err + } + vh.Routes = append(vh.Routes, rs...) + } + + if len(vh.Routes) > 0 { + virtualHosts = append(virtualHosts, vh) + } + } + + vh, err := b.buildVirtualHost(cfg.Options, "catch-all", "*", false) + if err != nil { + return nil, err + } + virtualHosts = append(virtualHosts, vh) + + rc, err := b.buildRouteConfiguration("main", virtualHosts) + if err != nil { + return nil, err + } + + return rc, nil +} diff --git a/config/envoyconfig/testdata/main_http_connection_manager_filter.json b/config/envoyconfig/testdata/main_http_connection_manager_filter.json index be7016000..661b36b1d 100644 --- a/config/envoyconfig/testdata/main_http_connection_manager_filter.json +++ b/config/envoyconfig/testdata/main_http_connection_manager_filter.json @@ -120,506 +120,12 @@ ] }, "requestTimeout": "30s", - "routeConfig": { - "name": "main", - "validateClusters": false, - "virtualHosts": [ - { - "domains": ["authenticate.example.com"], - "name": "authenticate.example.com", - "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" - } - } - ], - "routes": [ - { - "match": { - "path": "/.pomerium/jwt" - }, - "name": "pomerium-path-/.pomerium/jwt", - "route": { - "cluster": "pomerium-control-plane-http" - } - }, - { - "match": { - "path": "/.pomerium/webauthn" - }, - "name": "pomerium-path-/.pomerium/webauthn", - "route": { - "cluster": "pomerium-control-plane-http" - } - }, - { - "match": { - "path": "/ping" - }, - "name": "pomerium-path-/ping", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/healthz" - }, - "name": "pomerium-path-/healthz", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/.pomerium" - }, - "name": "pomerium-path-/.pomerium", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "prefix": "/.pomerium/" - }, - "name": "pomerium-prefix-/.pomerium/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/.well-known/pomerium" - }, - "name": "pomerium-path-/.well-known/pomerium", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "prefix": "/.well-known/pomerium/" - }, - "name": "pomerium-prefix-/.well-known/pomerium/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/robots.txt" - }, - "name": "pomerium-path-/robots.txt", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/oauth2/callback" - }, - "name": "pomerium-path-/oauth2/callback", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/" - }, - "name": "pomerium-path-/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - } - ] - }, - { - "domains": ["authenticate.example.com:443"], - "name": "authenticate.example.com:443", - "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" - } - } - ], - "routes": [ - { - "match": { - "path": "/.pomerium/jwt" - }, - "name": "pomerium-path-/.pomerium/jwt", - "route": { - "cluster": "pomerium-control-plane-http" - } - }, - { - "match": { - "path": "/.pomerium/webauthn" - }, - "name": "pomerium-path-/.pomerium/webauthn", - "route": { - "cluster": "pomerium-control-plane-http" - } - }, - { - "match": { - "path": "/ping" - }, - "name": "pomerium-path-/ping", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/healthz" - }, - "name": "pomerium-path-/healthz", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/.pomerium" - }, - "name": "pomerium-path-/.pomerium", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "prefix": "/.pomerium/" - }, - "name": "pomerium-prefix-/.pomerium/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/.well-known/pomerium" - }, - "name": "pomerium-path-/.well-known/pomerium", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "prefix": "/.well-known/pomerium/" - }, - "name": "pomerium-prefix-/.well-known/pomerium/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/robots.txt" - }, - "name": "pomerium-path-/robots.txt", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/oauth2/callback" - }, - "name": "pomerium-path-/oauth2/callback", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/" - }, - "name": "pomerium-path-/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - } - ] - }, - { - "domains": ["*"], - "name": "catch-all", - "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" - } - } - ], - "routes": [ - { - "match": { - "path": "/.pomerium/jwt" - }, - "name": "pomerium-path-/.pomerium/jwt", - "route": { - "cluster": "pomerium-control-plane-http" - } - }, - { - "match": { - "path": "/.pomerium/webauthn" - }, - "name": "pomerium-path-/.pomerium/webauthn", - "route": { - "cluster": "pomerium-control-plane-http" - } - }, - { - "match": { - "path": "/ping" - }, - "name": "pomerium-path-/ping", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/healthz" - }, - "name": "pomerium-path-/healthz", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/.pomerium" - }, - "name": "pomerium-path-/.pomerium", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "prefix": "/.pomerium/" - }, - "name": "pomerium-prefix-/.pomerium/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/.well-known/pomerium" - }, - "name": "pomerium-path-/.well-known/pomerium", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "prefix": "/.well-known/pomerium/" - }, - "name": "pomerium-prefix-/.well-known/pomerium/", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - }, - { - "match": { - "path": "/robots.txt" - }, - "name": "pomerium-path-/robots.txt", - "route": { - "cluster": "pomerium-control-plane-http" - }, - "typedPerFilterConfig": { - "envoy.filters.http.ext_authz": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", - "disabled": true - } - } - } - ] - } - ] + "rds": { + "configSource": { + "ads": {}, + "resourceApiVersion": "V3" + }, + "routeConfigName": "main" }, "skipXffAppend": true, "statPrefix": "ingress", diff --git a/go.mod b/go.mod index faf994209..2a5420161 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/natefinch/atomic v1.0.1 - github.com/open-policy-agent/opa v0.51.0 + github.com/open-policy-agent/opa v0.49.2 github.com/openzipkin/zipkin-go v0.4.1 github.com/ory/dockertest/v3 v3.9.1 github.com/peterbourgon/ff/v3 v3.3.0 diff --git a/go.sum b/go.sum index 7d24a7277..607fbc3e5 100644 --- a/go.sum +++ b/go.sum @@ -281,7 +281,7 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4 github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= +github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -314,7 +314,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= @@ -693,8 +693,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= -github.com/open-policy-agent/opa v0.51.0 h1:2hS5xhos8HtkN+mgpqMhNJSFtn/1n/h3wh+AeTPJg6Q= -github.com/open-policy-agent/opa v0.51.0/go.mod h1:OjmwLfXdeR7skSxrt8Yd3ScXTqPxyJn7GeTRJrcEerU= +github.com/open-policy-agent/opa v0.49.2 h1:n8ntRq/yDWy+cmYaqSLrHXmrT3tX8WlK28vjFQdC6W8= +github.com/open-policy-agent/opa v0.49.2/go.mod h1:7L3lN5qe8xboRmEHxC5lGjo5KsRMdK+CCLiFoOCP7rU= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -1237,7 +1237,7 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/controlplane/xds.go b/internal/controlplane/xds.go index 7ceee105b..c19bc8e89 100644 --- a/internal/controlplane/xds.go +++ b/internal/controlplane/xds.go @@ -11,8 +11,9 @@ import ( ) const ( - clusterTypeURL = "type.googleapis.com/envoy.config.cluster.v3.Cluster" - listenerTypeURL = "type.googleapis.com/envoy.config.listener.v3.Listener" + clusterTypeURL = "type.googleapis.com/envoy.config.cluster.v3.Cluster" + listenerTypeURL = "type.googleapis.com/envoy.config.listener.v3.Listener" + routeConfigurationTypeURL = "type.googleapis.com/envoy.config.route.v3.RouteConfiguration" ) func (srv *Server) buildDiscoveryResources(ctx context.Context) (map[string][]*envoy_service_discovery_v3.Resource, error) { @@ -24,25 +25,36 @@ func (srv *Server) buildDiscoveryResources(ctx context.Context) (map[string][]*e return nil, err } for _, cluster := range clusters { - any := protoutil.NewAny(cluster) resources[clusterTypeURL] = append(resources[clusterTypeURL], &envoy_service_discovery_v3.Resource{ Name: cluster.Name, Version: hex.EncodeToString(cryptutil.HashProto(cluster)), - Resource: any, + Resource: protoutil.NewAny(cluster), }) } - listeners, err := srv.Builder.BuildListeners(ctx, cfg.Config) + listeners, err := srv.Builder.BuildListeners(ctx, cfg.Config, false) if err != nil { return nil, err } for _, listener := range listeners { - any := protoutil.NewAny(listener) resources[listenerTypeURL] = append(resources[listenerTypeURL], &envoy_service_discovery_v3.Resource{ Name: listener.Name, Version: hex.EncodeToString(cryptutil.HashProto(listener)), - Resource: any, + Resource: protoutil.NewAny(listener), }) } + + routeConfigurations, err := srv.Builder.BuildRouteConfigurations(ctx, cfg.Config) + if err != nil { + return nil, err + } + for _, routeConfiguration := range routeConfigurations { + resources[routeConfigurationTypeURL] = append(resources[routeConfigurationTypeURL], &envoy_service_discovery_v3.Resource{ + Name: routeConfiguration.Name, + Version: hex.EncodeToString(cryptutil.HashProto(routeConfiguration)), + Resource: protoutil.NewAny(routeConfiguration), + }) + } + return resources, nil } diff --git a/pkg/envoy/envoy.go b/pkg/envoy/envoy.go index e32cfc299..9f99ddd97 100644 --- a/pkg/envoy/envoy.go +++ b/pkg/envoy/envoy.go @@ -19,8 +19,6 @@ import ( "time" "github.com/cenkalti/backoff/v4" - envoy_config_bootstrap_v3 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3" - envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" "github.com/google/go-cmp/cmp" "github.com/natefinch/atomic" "github.com/rs/zerolog" @@ -30,7 +28,6 @@ import ( "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config/envoyconfig" "github.com/pomerium/pomerium/internal/log" - "github.com/pomerium/pomerium/internal/telemetry" "github.com/pomerium/pomerium/pkg/envoy/files" ) @@ -185,7 +182,7 @@ func (srv *Server) run(ctx context.Context, cfg *config.Config) error { } func (srv *Server) writeConfig(ctx context.Context, cfg *config.Config) error { - confBytes, err := srv.buildBootstrapConfig(cfg) + confBytes, err := srv.buildBootstrapConfig(ctx, cfg) if err != nil { return err } @@ -196,65 +193,12 @@ func (srv *Server) writeConfig(ctx context.Context, cfg *config.Config) error { return atomic.WriteFile(cfgPath, bytes.NewReader(confBytes)) } -func (srv *Server) buildBootstrapConfig(cfg *config.Config) ([]byte, error) { - nodeCfg := &envoy_config_core_v3.Node{ - Id: telemetry.ServiceName(cfg.Options.Services), - Cluster: telemetry.ServiceName(cfg.Options.Services), - } - - adminCfg, err := srv.builder.BuildBootstrapAdmin(cfg) +func (srv *Server) buildBootstrapConfig(ctx context.Context, cfg *config.Config) ([]byte, error) { + bootstrapCfg, err := srv.builder.BuildBootstrap(ctx, cfg, false) if err != nil { return nil, err } - dynamicCfg := &envoy_config_bootstrap_v3.Bootstrap_DynamicResources{ - AdsConfig: &envoy_config_core_v3.ApiConfigSource{ - ApiType: envoy_config_core_v3.ApiConfigSource_ApiType(envoy_config_core_v3.ApiConfigSource_ApiType_value["DELTA_GRPC"]), - TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, - GrpcServices: []*envoy_config_core_v3.GrpcService{ - { - TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{ - EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{ - ClusterName: "pomerium-control-plane-grpc", - }, - }, - }, - }, - }, - LdsConfig: &envoy_config_core_v3.ConfigSource{ - ResourceApiVersion: envoy_config_core_v3.ApiVersion_V3, - ConfigSourceSpecifier: &envoy_config_core_v3.ConfigSource_Ads{}, - }, - CdsConfig: &envoy_config_core_v3.ConfigSource{ - ResourceApiVersion: envoy_config_core_v3.ApiVersion_V3, - ConfigSourceSpecifier: &envoy_config_core_v3.ConfigSource_Ads{}, - }, - } - - staticCfg, err := srv.builder.BuildBootstrapStaticResources() - if err != nil { - return nil, err - } - - statsCfg, err := srv.builder.BuildBootstrapStatsConfig(cfg) - if err != nil { - return nil, err - } - - layeredRuntimeCfg, err := srv.builder.BuildBootstrapLayeredRuntime() - if err != nil { - return nil, err - } - - bootstrapCfg := &envoy_config_bootstrap_v3.Bootstrap{ - Node: nodeCfg, - Admin: adminCfg, - DynamicResources: dynamicCfg, - StaticResources: staticCfg, - StatsConfig: statsCfg, - LayeredRuntime: layeredRuntimeCfg, - } - jsonBytes, err := protojson.Marshal(bootstrapCfg) if err != nil { return nil, err