diff --git a/config/options.go b/config/options.go index ba30dc300..cf3b96412 100644 --- a/config/options.go +++ b/config/options.go @@ -262,6 +262,9 @@ type Options struct { // If unset, the GCP metadata server will be used to query for identity tokens. GoogleCloudServerlessAuthenticationServiceAccount string `mapstructure:"google_cloud_serverless_authentication_service_account" yaml:"google_cloud_serverless_authentication_service_account,omitempty"` //nolint + // UseProxyProtocol configures the HTTP listener to require the HAProxy proxy protocol (either v1 or v2) on incoming requests. + UseProxyProtocol bool `mapstructure:"require_proxy_protocol" yaml:"require_proxy_protocol,omitempty" json:"require_proxy_protocol,omitempty"` + viper *viper.Viper AutocertOptions `mapstructure:",squash" yaml:",inline"` diff --git a/docs/reference/readme.md b/docs/reference/readme.md index fce1aefef..f337d9ae4 100644 --- a/docs/reference/readme.md +++ b/docs/reference/readme.md @@ -581,6 +581,15 @@ tracing_zipkin_endpoint | Url to the Zipkin HTTP endpoint. | ✅ ![jaeger example trace](./img/jaeger.png) +### Use Proxy Protocol +- Environment Variable: `USE_PROXY_PROTOCOL` +- Config File Key: `use_proxy_protocol` +- Type: `bool` +- Optional + +Setting `use_proxy_protocol` will configure Pomerium to require the [HAProxy proxy protocol](https://www.haproxy.org/download/1.9/doc/proxy-protocol.txt) on incoming connections. Versions 1 and 2 of the protocol are supported. + + ## Authenticate Service ### Authenticate Callback Path @@ -1227,7 +1236,7 @@ Remove Request Headers allows you to remove given request headers. This can be u - Optional - Example: `{ "host_redirect": "example.com" }` -`Redirect` is used to redirect incoming requests to a new URL. The `redirect` field is an object with several optional, +`Redirect` is used to redirect incoming requests to a new URL. The `redirect` field is an object with several possible options: - `https_redirect` (boolean): the incoming scheme will be swapped with "https". @@ -1236,7 +1245,6 @@ options: - `port_redirect` (integer): the incoming port will be swapped with the given value. - `path_redirect` (string): the incoming path portion of the URL will be swapped with the given value. - `prefix_rewrite` (string): the incoming matched prefix will be swapped with the given value. -- `regex_rewrite_pattern`, `regex_rewrite_substitution` (string): the incoming matched regex will be swapped with this value. - `response_code` (integer): the response code to use for the redirect. Defaults to 301. - `strip_query` (boolean): indicates that during redirection, the query portion of the URL will be removed. Defaults to false. diff --git a/docs/reference/settings.yaml b/docs/reference/settings.yaml index d5314a2bb..9d0388f4f 100644 --- a/docs/reference/settings.yaml +++ b/docs/reference/settings.yaml @@ -661,6 +661,15 @@ settings: #### Example ![jaeger example trace](./img/jaeger.png) + - name: "Use Proxy Protocol" + keys: ["use_proxy_protocol"] + attributes: | + - Environment Variable: `USE_PROXY_PROTOCOL` + - Config File Key: `use_proxy_protocol` + - Type: `bool` + - Optional + doc: | + Setting `use_proxy_protocol` will configure Pomerium to require the [HAProxy proxy protocol](https://www.haproxy.org/download/1.9/doc/proxy-protocol.txt) on incoming connections. Versions 1 and 2 of the protocol are supported. - name: "Authenticate Service" settings: - name: "Authenticate Callback Path" diff --git a/internal/controlplane/xds_listeners.go b/internal/controlplane/xds_listeners.go index de50622f2..564c4042b 100644 --- a/internal/controlplane/xds_listeners.go +++ b/internal/controlplane/xds_listeners.go @@ -12,6 +12,7 @@ import ( envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" envoy_extensions_filters_http_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3" envoy_extensions_filters_http_lua_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3" + envoy_extensions_filters_listener_proxy_protocol_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/proxy_protocol/v3" envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" @@ -52,13 +53,25 @@ func (srv *Server) buildListeners(options *config.Options) []*envoy_config_liste } func (srv *Server) buildMainListener(options *config.Options) *envoy_config_listener_v3.Listener { + listenerFilters := []*envoy_config_listener_v3.ListenerFilter{} + if options.UseProxyProtocol { + proxyCfg := marshalAny(&envoy_extensions_filters_listener_proxy_protocol_v3.ProxyProtocol{}) + listenerFilters = append(listenerFilters, &envoy_config_listener_v3.ListenerFilter{ + Name: "envoy.filters.listener.proxy_protocol", + ConfigType: &envoy_config_listener_v3.ListenerFilter_TypedConfig{ + TypedConfig: proxyCfg, + }, + }) + } + if options.InsecureServer { filter := buildMainHTTPConnectionManagerFilter(options, getAllRouteableDomains(options, options.Addr)) return &envoy_config_listener_v3.Listener{ - Name: "http-ingress", - Address: buildAddress(options.Addr, 80), + Name: "http-ingress", + Address: buildAddress(options.Addr, 80), + ListenerFilters: listenerFilters, FilterChains: []*envoy_config_listener_v3.FilterChain{{ Filters: []*envoy_config_listener_v3.Filter{ filter, @@ -68,15 +81,17 @@ func (srv *Server) buildMainListener(options *config.Options) *envoy_config_list } tlsInspectorCfg := marshalAny(new(emptypb.Empty)) + listenerFilters = append(listenerFilters, &envoy_config_listener_v3.ListenerFilter{ + Name: "envoy.filters.listener.tls_inspector", + ConfigType: &envoy_config_listener_v3.ListenerFilter_TypedConfig{ + TypedConfig: tlsInspectorCfg, + }, + }) + li := &envoy_config_listener_v3.Listener{ - Name: "https-ingress", - Address: buildAddress(options.Addr, 443), - ListenerFilters: []*envoy_config_listener_v3.ListenerFilter{{ - Name: "envoy.filters.listener.tls_inspector", - ConfigType: &envoy_config_listener_v3.ListenerFilter_TypedConfig{ - TypedConfig: tlsInspectorCfg, - }, - }}, + Name: "https-ingress", + Address: buildAddress(options.Addr, 443), + ListenerFilters: listenerFilters, FilterChains: buildFilterChains(options, options.Addr, func(tlsDomain string, httpDomains []string) *envoy_config_listener_v3.FilterChain { filter := buildMainHTTPConnectionManagerFilter(options, httpDomains) diff --git a/internal/controlplane/xds_listeners_test.go b/internal/controlplane/xds_listeners_test.go index 14f06abbf..1fee62515 100644 --- a/internal/controlplane/xds_listeners_test.go +++ b/internal/controlplane/xds_listeners_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/pomerium/pomerium/config" + "github.com/pomerium/pomerium/internal/controlplane/filemgr" "github.com/pomerium/pomerium/internal/testutil" "github.com/pomerium/pomerium/pkg/cryptutil" ) @@ -500,3 +501,30 @@ func Test_buildRouteConfiguration(t *testing.T) { assert.Equal(t, virtualHosts, routeConfig.GetVirtualHosts()) assert.False(t, routeConfig.GetValidateClusters().GetValue()) } + +func Test_requireProxyProtocol(t *testing.T) { + srv := &Server{ + filemgr: filemgr.NewManager(), + } + t.Run("required", func(t *testing.T) { + li := srv.buildMainListener(&config.Options{ + UseProxyProtocol: true, + InsecureServer: true, + }) + testutil.AssertProtoJSONEqual(t, `[ + { + "name": "envoy.filters.listener.proxy_protocol", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.proxy_protocol.v3.ProxyProtocol" + } + } + ]`, li.GetListenerFilters()) + }) + t.Run("not required", func(t *testing.T) { + li := srv.buildMainListener(&config.Options{ + UseProxyProtocol: false, + InsecureServer: true, + }) + assert.Len(t, li.GetListenerFilters(), 0) + }) +}