From f72c51ad8b0084ab89a02c3efc66cc8dd83f85d0 Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Sun, 23 Feb 2025 00:57:32 +0400 Subject: [PATCH 01/14] This PR introduces the ForwardUsernameHeader field in the BasicAuth section of SecurityPolicy. It enables the Envoy to forward the username of a successfully authenticated user to the backend services via a specified HTTP header. The field is optional. If it's not specified, the username will not be forwarded. Signed-off-by: Suren Raju --- api/v1alpha1/basic_auth_types.go | 6 ++ ...ateway.envoyproxy.io_securitypolicies.yaml | 5 ++ internal/gatewayapi/securitypolicy.go | 5 +- internal/ir/xds.go | 6 ++ internal/ir/xds_test.go | 2 +- internal/xds/translator/basicauth.go | 67 +++++++++------ .../in/xds-ir/basic-auth-username-header.yaml | 82 +++++++++++++++++++ .../basic-auth-username-header.clusters.yaml | 51 ++++++++++++ .../basic-auth-username-header.endpoints.yaml | 36 ++++++++ .../basic-auth-username-header.listeners.yaml | 48 +++++++++++ .../basic-auth-username-header.routes.yaml | 47 +++++++++++ .../out/xds-ir/basic-auth.listeners.yaml | 8 +- .../out/xds-ir/basic-auth.routes.yaml | 6 +- .../xds-ir/custom-filter-order.listeners.yaml | 2 +- .../xds-ir/custom-filter-order.routes.yaml | 2 +- ...port-with-different-filters.listeners.yaml | 4 +- ...me-port-with-different-filters.routes.yaml | 2 +- 17 files changed, 340 insertions(+), 39 deletions(-) create mode 100644 internal/xds/translator/testdata/in/xds-ir/basic-auth-username-header.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.routes.yaml diff --git a/api/v1alpha1/basic_auth_types.go b/api/v1alpha1/basic_auth_types.go index f7bec28378..3dc3f86aa3 100644 --- a/api/v1alpha1/basic_auth_types.go +++ b/api/v1alpha1/basic_auth_types.go @@ -26,4 +26,10 @@ type BasicAuth struct { // // Note: The secret must be in the same namespace as the SecurityPolicy. Users gwapiv1.SecretObjectReference `json:"users"` + + // This field specifies the header name to forward a successfully authenticated user to + // the backend. The header will be added to the request with the username as the value. + // + // If it is not specified, the username will not be forwarded. + ForwardUsernameHeader string `json:"forwardUsernameHeader"` } diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index 663507fbfe..05cd6b159d 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -311,6 +311,11 @@ spec: description: BasicAuth defines the configuration for the HTTP Basic Authentication. properties: + forwardUsernameHeader: + description: |- + Header used to forward the username of a successfully authenticated user to the backend. + If it is not specified, the username will not be forwarded. + type: string users: description: |- The Kubernetes secret which contains the username-password pairs in diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 9d35053181..64ff5cc2f2 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -1119,8 +1119,9 @@ func (t *Translator) buildBasicAuth( } return &ir.BasicAuth{ - Name: irConfigName(policy), - Users: usersSecretBytes, + Name: irConfigName(policy), + Users: usersSecretBytes, + ForwardUsernameHeader: basicAuth.ForwardUsernameHeader, }, nil } diff --git a/internal/ir/xds.go b/internal/ir/xds.go index a9a0ed7397..f79adcd9bb 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -1081,6 +1081,12 @@ type BasicAuth struct { // The username-password pairs in htpasswd format. Users PrivateBytes `json:"users,omitempty" yaml:"users,omitempty"` + + // This field specifies the header name to forward a successfully authenticated user to + // the backend. The header will be added to the request with the username as the value. + // + // If it is not specified, the username will not be forwarded. + ForwardUsernameHeader string `json:"forwardUsernameHeader" yaml:"forwardUsernameHeader"` } // APIKeyAuth defines the schema for the API Key Authentication. diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index cfbd84c913..3302bef331 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -1365,7 +1365,7 @@ func TestRedaction(t *testing.T) { `"name":"","hostname":"","isHTTP2":false,"security":{` + `"oidc":{"name":"","provider":{},"clientID":"","clientSecret":"[redacted]","hmacSecret":"[redacted]"},` + `"apiKeyAuth":{"credentials":{"client-id":"[redacted]"},"extractFrom":null},` + - `"basicAuth":{"name":"","users":"[redacted]"}` + + `"basicAuth":{"name":"","users":"[redacted]","forwardUsernameHeader":""}` + `}}],` + `"isHTTP2":false,"path":{"mergeSlashes":false,"escapedSlashesAction":""}}]}`, }, diff --git a/internal/xds/translator/basicauth.go b/internal/xds/translator/basicauth.go index 4d22377c74..1300135336 100644 --- a/internal/xds/translator/basicauth.go +++ b/internal/xds/translator/basicauth.go @@ -29,8 +29,12 @@ type basicAuth struct{} var _ httpFilter = &basicAuth{} -// patchHCM builds and appends the basic_auth Filter to the HTTP Connection Manager -// if applicable, and it does not already exist. +// patchHCM updates the HTTPConnectionManager with a Basic Auth HTTP filter for routes requiring authentication. +// It scans through all routes in the provided HTTPListener, and if a route has a BasicAuth configuration, +// it checks for the presence of a corresponding filter in the manager. If the filter is not already present, +// it generates and appends a new Basic Auth filter. +// The function returns an error if either the HTTPConnectionManager or HTTPListener is nil, or if an error occurs +// during the filter creation process. func (*basicAuth) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error { if mgr == nil { return errors.New("hcm is nil") @@ -38,35 +42,36 @@ func (*basicAuth) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTP if irListener == nil { return errors.New("ir listener is nil") } - if hcmContainsFilter(mgr, egv1a1.EnvoyFilterBasicAuth.String()) { - return nil - } - var ( - irBasicAuth *ir.BasicAuth - filter *hcmv3.HttpFilter - err error - ) + var errs error for _, route := range irListener.Routes { - if route.Security != nil && route.Security.BasicAuth != nil { - irBasicAuth = route.Security.BasicAuth - break + if route.Security == nil || route.Security.BasicAuth == nil { + continue } - } - if irBasicAuth == nil { - return nil - } - // We use the first route that contains the basicAuth config to build the filter. - // The HCM-level filter config doesn't matter since it is overridden at the route level. - if filter, err = buildHCMBasicAuthFilter(irBasicAuth); err != nil { - return err + // Only generates one Basic Auth Envoy filter for each unique name. + // For example, if there are two routes under the same gateway with the + // same BasicAuth config, only one BasicAuth filter will be generated. + if hcmContainsFilter(mgr, basicAuthFilterName(route.Security.BasicAuth)) { + continue + } + + filter, err := buildHCMBasicAuthFilter(route.Security.BasicAuth) + if err != nil { + errs = errors.Join(errs, err) + continue + } + + mgr.HttpFilters = append(mgr.HttpFilters, filter) } - mgr.HttpFilters = append(mgr.HttpFilters, filter) - return err + + return errs } +// patchHCM builds and appends the basic_auth Filter to the HTTP Connection Manager +// if applicable, and it does not already exist. + // buildHCMBasicAuthFilter returns a basic_auth HTTP filter from the provided IR HTTPRoute. func buildHCMBasicAuthFilter(basicAuth *ir.BasicAuth) (*hcmv3.HttpFilter, error) { var ( @@ -82,13 +87,17 @@ func buildHCMBasicAuthFilter(basicAuth *ir.BasicAuth) (*hcmv3.HttpFilter, error) }, }, } + // Set the ForwardUsernameHeader field if it is specified. + if basicAuth.ForwardUsernameHeader != "" { + basicAuthProto.ForwardUsernameHeader = basicAuth.ForwardUsernameHeader + } if basicAuthAny, err = proto.ToAnyWithValidation(basicAuthProto); err != nil { return nil, err } return &hcmv3.HttpFilter{ - Name: egv1a1.EnvoyFilterBasicAuth.String(), + Name: basicAuthFilterName(basicAuth), ConfigType: &hcmv3.HttpFilter_TypedConfig{ TypedConfig: basicAuthAny, }, @@ -96,6 +105,10 @@ func buildHCMBasicAuthFilter(basicAuth *ir.BasicAuth) (*hcmv3.HttpFilter, error) }, nil } +func basicAuthFilterName(basicAuth *ir.BasicAuth) string { + return perRouteFilterName(egv1a1.EnvoyFilterBasicAuth, basicAuth.Name) +} + func (*basicAuth) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) error { return nil } @@ -118,9 +131,9 @@ func (*basicAuth) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error basicAuthAny *anypb.Any err error ) - + filterName := basicAuthFilterName(irRoute.Security.BasicAuth) perFilterCfg = route.GetTypedPerFilterConfig() - if _, ok := perFilterCfg[egv1a1.EnvoyFilterBasicAuth.String()]; ok { + if _, ok := perFilterCfg[filterName]; ok { // This should not happen since this is the only place where the filter // config is added in a route. return fmt.Errorf("route already contains filter config: %s, %+v", @@ -136,7 +149,7 @@ func (*basicAuth) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error if perFilterCfg == nil { route.TypedPerFilterConfig = make(map[string]*anypb.Any) } - route.TypedPerFilterConfig[egv1a1.EnvoyFilterBasicAuth.String()] = basicAuthAny + route.TypedPerFilterConfig[filterName] = basicAuthAny return nil } diff --git a/internal/xds/translator/testdata/in/xds-ir/basic-auth-username-header.yaml b/internal/xds/translator/testdata/in/xds-ir/basic-auth-username-header.yaml new file mode 100644 index 0000000000..81ab698e95 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/basic-auth-username-header.yaml @@ -0,0 +1,82 @@ +http: +- address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: default/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + hostname: www.foo.com + isHTTP2: false + pathMatch: + distinct: false + name: "" + prefix: /foo1 + backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + security: + basicAuth: + name: securitypolicy/default/policy-for-http-route-1 + users: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= + forwardUsernameHeader: x-username + - name: httproute/default/httproute-1/rule/1/match/0/www_foo_com + backendWeights: + hostname: www.foo.com + isHTTP2: false + pathMatch: + distinct: false + name: "" + prefix: /foo2 + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/1 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + security: + basicAuth: + name: securitypolicy/default/policy-for-http-route-1 + users: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= + - name: httproute/default/httproute-2/rule/0/match/0/www_bar_com + hostname: www.bar.com + isHTTP2: false + pathMatch: + distinct: false + name: "" + prefix: /bar + backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + security: + basicAuth: + name: securitypolicy/default/policy-for-gateway-1 + users: Zm9vOntTSEF9WXMyM0FnLzVJT1dxWkN3OVFHYVZEZEh3SDAwPQpmb28xOntTSEF9ZGpaMTFxSFkwS09pamV5bUs3YUt2WXV2aHZNPQo= + forwardUsernameHeader: x-user-id diff --git a/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.clusters.yaml new file mode 100644 index 0000000000..c60ba5e19a --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.clusters.yaml @@ -0,0 +1,51 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-1/rule/0 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-1/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-1/rule/1 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-1/rule/1 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-2/rule/0 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-2/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml new file mode 100644 index 0000000000..bf9f002378 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml @@ -0,0 +1,36 @@ +- clusterName: httproute/default/httproute-1/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-1/rule/0/backend/0 +- clusterName: httproute/default/httproute-1/rule/1 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-1/rule/1/backend/0 +- clusterName: httproute/default/httproute-2/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-2/rule/0/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.listeners.yaml new file mode 100644 index 0000000000..1ab0be3569 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.listeners.yaml @@ -0,0 +1,48 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - disabled: true + name: envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth + forwardUsernameHeader: x-username + users: + inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= + - disabled: true + name: envoy.filters.http.basic_auth/securitypolicy/default/policy-for-gateway-1 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth + forwardUsernameHeader: x-user-id + users: + inlineBytes: Zm9vOntTSEF9WXMyM0FnLzVJT1dxWkN3OVFHYVZEZEh3SDAwPQpmb28xOntTSEF9ZGpaMTFxSFkwS09pamV5bUs3YUt2WXV2aHZNPQo= + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: default/gateway-1/http + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10080 + useRemoteAddress: true + name: default/gateway-1/http + name: default/gateway-1/http + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.routes.yaml new file mode 100644 index 0000000000..114de4cb02 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.routes.yaml @@ -0,0 +1,47 @@ +- ignorePortInHostMatching: true + name: default/gateway-1/http + virtualHosts: + - domains: + - www.foo.com + name: default/gateway-1/http/www_foo_com + routes: + - match: + pathSeparatedPrefix: /foo1 + name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + route: + cluster: httproute/default/httproute-1/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1: + '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute + users: + inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= + - match: + pathSeparatedPrefix: /foo2 + name: httproute/default/httproute-1/rule/1/match/0/www_foo_com + route: + cluster: httproute/default/httproute-1/rule/1 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1: + '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute + users: + inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= + - domains: + - www.bar.com + name: default/gateway-1/http/www_bar_com + routes: + - match: + pathSeparatedPrefix: /bar + name: httproute/default/httproute-2/rule/0/match/0/www_bar_com + route: + cluster: httproute/default/httproute-2/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.basic_auth/securitypolicy/default/policy-for-gateway-1: + '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute + users: + inlineBytes: Zm9vOntTSEF9WXMyM0FnLzVJT1dxWkN3OVFHYVZEZEh3SDAwPQpmb28xOntTSEF9ZGpaMTFxSFkwS09pamV5bUs3YUt2WXV2aHZNPQo= diff --git a/internal/xds/translator/testdata/out/xds-ir/basic-auth.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/basic-auth.listeners.yaml index a7accc0ef6..686a1d85b7 100644 --- a/internal/xds/translator/testdata/out/xds-ir/basic-auth.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/basic-auth.listeners.yaml @@ -15,11 +15,17 @@ maxConcurrentStreams: 100 httpFilters: - disabled: true - name: envoy.filters.http.basic_auth + name: envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth users: inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= + - disabled: true + name: envoy.filters.http.basic_auth/securitypolicy/default/policy-for-gateway-1 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth + users: + inlineBytes: Zm9vOntTSEF9WXMyM0FnLzVJT1dxWkN3OVFHYVZEZEh3SDAwPQpmb28xOntTSEF9ZGpaMTFxSFkwS09pamV5bUs3YUt2WXV2aHZNPQo= - name: envoy.filters.http.router typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router diff --git a/internal/xds/translator/testdata/out/xds-ir/basic-auth.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/basic-auth.routes.yaml index 7e51086d2e..114de4cb02 100644 --- a/internal/xds/translator/testdata/out/xds-ir/basic-auth.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/basic-auth.routes.yaml @@ -13,7 +13,7 @@ upgradeConfigs: - upgradeType: websocket typedPerFilterConfig: - envoy.filters.http.basic_auth: + envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute users: inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= @@ -25,7 +25,7 @@ upgradeConfigs: - upgradeType: websocket typedPerFilterConfig: - envoy.filters.http.basic_auth: + envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute users: inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= @@ -41,7 +41,7 @@ upgradeConfigs: - upgradeType: websocket typedPerFilterConfig: - envoy.filters.http.basic_auth: + envoy.filters.http.basic_auth/securitypolicy/default/policy-for-gateway-1: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute users: inlineBytes: Zm9vOntTSEF9WXMyM0FnLzVJT1dxWkN3OVFHYVZEZEh3SDAwPQpmb28xOntTSEF9ZGpaMTFxSFkwS09pamV5bUs3YUt2WXV2aHZNPQo= diff --git a/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml index 9758fe7f17..7003a2ea33 100644 --- a/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml @@ -18,7 +18,7 @@ typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors - disabled: true - name: envoy.filters.http.basic_auth + name: envoy.filters.http.basic_auth/securitypolicy/envoy-gateway/policy-for-gateway typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth users: diff --git a/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.routes.yaml index 5299f2ff4f..c6e9c1d93d 100644 --- a/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.routes.yaml @@ -11,7 +11,7 @@ pathSeparatedPrefix: /foo name: httproute/envoy-gateway/httproute-1/rule/0/match/0/www_example_com typedPerFilterConfig: - envoy.filters.http.basic_auth: + envoy.filters.http.basic_auth/securitypolicy/envoy-gateway/policy-for-gateway: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute users: inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= diff --git a/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml index 39bfe9f587..5c6552fd10 100755 --- a/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml @@ -37,7 +37,7 @@ uri: http://http-backend.envoy-gateway:80/auth transportApiVersion: V3 - disabled: true - name: envoy.filters.http.basic_auth + name: envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth users: @@ -99,7 +99,7 @@ uri: http://http-backend.envoy-gateway:80/auth transportApiVersion: V3 - disabled: true - name: envoy.filters.http.basic_auth + name: envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth users: diff --git a/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.routes.yaml index ae520e2676..5ce26e0d75 100755 --- a/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.routes.yaml @@ -18,7 +18,7 @@ upgradeConfigs: - upgradeType: websocket typedPerFilterConfig: - envoy.filters.http.basic_auth: + envoy.filters.http.basic_auth/securitypolicy/default/policy-for-http-route-1: '@type': type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute users: inlineBytes: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo= From f04370bafde82672eeaebd0c78cdd947c578d9bb Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Sun, 23 Feb 2025 18:52:27 +0400 Subject: [PATCH 02/14] fix: test case data Signed-off-by: Suren Raju --- internal/gatewayapi/testdata/custom-filter-order.out.yaml | 2 ++ .../testdata/securitypolicy-with-basic-auth.out.yaml | 5 +++++ site/content/en/latest/api/extension_types.md | 1 + 3 files changed, 8 insertions(+) diff --git a/internal/gatewayapi/testdata/custom-filter-order.out.yaml b/internal/gatewayapi/testdata/custom-filter-order.out.yaml index 685d6b7fe5..e60114875f 100644 --- a/internal/gatewayapi/testdata/custom-filter-order.out.yaml +++ b/internal/gatewayapi/testdata/custom-filter-order.out.yaml @@ -165,6 +165,7 @@ securityPolicies: namespace: envoy-gateway spec: basicAuth: + forwardUsernameHeader: "" users: group: null kind: null @@ -279,6 +280,7 @@ xdsIR: prefix: /foo security: basicAuth: + forwardUsernameHeader: "" name: securitypolicy/envoy-gateway/policy-for-gateway users: '[redacted]' cors: diff --git a/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml index 283f4445de..b29a70a5ec 100644 --- a/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml @@ -147,6 +147,7 @@ securityPolicies: namespace: default spec: basicAuth: + forwardUsernameHeader: "" users: group: null kind: null @@ -178,6 +179,7 @@ securityPolicies: namespace: default spec: basicAuth: + forwardUsernameHeader: "" users: group: null kind: null @@ -249,6 +251,7 @@ xdsIR: prefix: /foo1 security: basicAuth: + forwardUsernameHeader: "" name: securitypolicy/default/policy-for-http-route-1 users: '[redacted]' - destination: @@ -273,6 +276,7 @@ xdsIR: prefix: /foo2 security: basicAuth: + forwardUsernameHeader: "" name: securitypolicy/default/policy-for-http-route-1 users: '[redacted]' - destination: @@ -297,6 +301,7 @@ xdsIR: prefix: /bar security: basicAuth: + forwardUsernameHeader: "" name: securitypolicy/default/policy-for-gateway-1 users: '[redacted]' readyListener: diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 92aacca6ab..bd72771f7c 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -483,6 +483,7 @@ _Appears in:_ | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `users` | _[SecretObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.SecretObjectReference)_ | true | | The Kubernetes secret which contains the username-password pairs in
htpasswd format, used to verify user credentials in the "Authorization"
header.
This is an Opaque secret. The username-password pairs should be stored in
the key ".htpasswd". As the key name indicates, the value needs to be the
htpasswd format, for example: "user1:\{SHA\}hashed_user1_password".
Right now, only SHA hash algorithm is supported.
Reference to https://httpd.apache.org/docs/2.4/programs/htpasswd.html
for more details.
Note: The secret must be in the same namespace as the SecurityPolicy. | +| `forwardUsernameHeader` | _string_ | true | | This field specifies the header name to forward a successfully authenticated user to
the backend. The header will be added to the request with the username as the value.
If it is not specified, the username will not be forwarded. | #### BodyToExtAuth From cf2acfe6a1e4a715eb524b6cb4faf353adf0c83e Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Sun, 23 Feb 2025 21:42:28 +0400 Subject: [PATCH 03/14] fix: test failures Signed-off-by: Suren Raju --- api/v1alpha1/basic_auth_types.go | 2 +- .../generated/gateway.envoyproxy.io_securitypolicies.yaml | 4 +++- internal/gatewayapi/testdata/custom-filter-order.out.yaml | 1 - .../testdata/securitypolicy-with-basic-auth.out.yaml | 2 -- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/basic_auth_types.go b/api/v1alpha1/basic_auth_types.go index 3dc3f86aa3..7beb986d43 100644 --- a/api/v1alpha1/basic_auth_types.go +++ b/api/v1alpha1/basic_auth_types.go @@ -31,5 +31,5 @@ type BasicAuth struct { // the backend. The header will be added to the request with the username as the value. // // If it is not specified, the username will not be forwarded. - ForwardUsernameHeader string `json:"forwardUsernameHeader"` + ForwardUsernameHeader string `json:"forwardUsernameHeader,omitempty"` } diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index f8c3cb058b..5abc028ae4 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -386,7 +386,9 @@ spec: properties: forwardUsernameHeader: description: |- - Header used to forward the username of a successfully authenticated user to the backend. + This field specifies the header name to forward a successfully authenticated user to + the backend. The header will be added to the request with the username as the value. + If it is not specified, the username will not be forwarded. type: string users: diff --git a/internal/gatewayapi/testdata/custom-filter-order.out.yaml b/internal/gatewayapi/testdata/custom-filter-order.out.yaml index e60114875f..a313d440f3 100644 --- a/internal/gatewayapi/testdata/custom-filter-order.out.yaml +++ b/internal/gatewayapi/testdata/custom-filter-order.out.yaml @@ -165,7 +165,6 @@ securityPolicies: namespace: envoy-gateway spec: basicAuth: - forwardUsernameHeader: "" users: group: null kind: null diff --git a/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml index b29a70a5ec..dc72ba9bd7 100644 --- a/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml @@ -147,7 +147,6 @@ securityPolicies: namespace: default spec: basicAuth: - forwardUsernameHeader: "" users: group: null kind: null @@ -179,7 +178,6 @@ securityPolicies: namespace: default spec: basicAuth: - forwardUsernameHeader: "" users: group: null kind: null From 76a685f39c157bea53080b8a259c845b8a9239d7 Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Sun, 2 Mar 2025 10:13:50 +0400 Subject: [PATCH 04/14] fix: failing tests Signed-off-by: Suren Raju --- api/v1alpha1/basic_auth_types.go | 3 +-- api/v1alpha1/zz_generated.deepcopy.go | 5 ----- .../out/xds-ir/basic-auth-username-header.endpoints.yaml | 9 +++------ site/content/en/latest/api/extension_types.md | 2 +- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/api/v1alpha1/basic_auth_types.go b/api/v1alpha1/basic_auth_types.go index cc98c1e6f0..5ed89b762d 100644 --- a/api/v1alpha1/basic_auth_types.go +++ b/api/v1alpha1/basic_auth_types.go @@ -33,6 +33,5 @@ type BasicAuth struct { // If it is not specified, the username will not be forwarded. // // +optional - // +notImplementedHide - ForwardUsernameHeader *string `json:"forwardUsernameHeader,omitempty"` + ForwardUsernameHeader string `json:"forwardUsernameHeader,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bbb7c11455..97fddd3ff6 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -653,11 +653,6 @@ func (in *BackendTrafficPolicySpec) DeepCopy() *BackendTrafficPolicySpec { func (in *BasicAuth) DeepCopyInto(out *BasicAuth) { *out = *in in.Users.DeepCopyInto(&out.Users) - if in.ForwardUsernameHeader != nil { - in, out := &in.ForwardUsernameHeader, &out.ForwardUsernameHeader - *out = new(string) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicAuth. diff --git a/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml index bf9f002378..d8aa94029d 100644 --- a/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/basic-auth-username-header.endpoints.yaml @@ -8,8 +8,7 @@ portValue: 8080 loadBalancingWeight: 1 loadBalancingWeight: 1 - locality: - region: httproute/default/httproute-1/rule/0/backend/0 + locality: {} - clusterName: httproute/default/httproute-1/rule/1 endpoints: - lbEndpoints: @@ -20,8 +19,7 @@ portValue: 8080 loadBalancingWeight: 1 loadBalancingWeight: 1 - locality: - region: httproute/default/httproute-1/rule/1/backend/0 + locality: {} - clusterName: httproute/default/httproute-2/rule/0 endpoints: - lbEndpoints: @@ -32,5 +30,4 @@ portValue: 8080 loadBalancingWeight: 1 loadBalancingWeight: 1 - locality: - region: httproute/default/httproute-2/rule/0/backend/0 + locality: {} diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 6ea4f543a6..a57c390b4e 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -499,7 +499,7 @@ _Appears in:_ | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `users` | _[SecretObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.SecretObjectReference)_ | true | | The Kubernetes secret which contains the username-password pairs in
htpasswd format, used to verify user credentials in the "Authorization"
header.
This is an Opaque secret. The username-password pairs should be stored in
the key ".htpasswd". As the key name indicates, the value needs to be the
htpasswd format, for example: "user1:\{SHA\}hashed_user1_password".
Right now, only SHA hash algorithm is supported.
Reference to https://httpd.apache.org/docs/2.4/programs/htpasswd.html
for more details.
Note: The secret must be in the same namespace as the SecurityPolicy. | -| `forwardUsernameHeader` | _string_ | true | | This field specifies the header name to forward a successfully authenticated user to
the backend. The header will be added to the request with the username as the value.
If it is not specified, the username will not be forwarded. | +| `forwardUsernameHeader` | _string_ | false | | This field specifies the header name to forward a successfully authenticated user to
the backend. The header will be added to the request with the username as the value.
If it is not specified, the username will not be forwarded. | #### BodyToExtAuth From a447da1818d0e4d4685bfa5e826e950b26b8ff8c Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Sun, 2 Mar 2025 16:58:10 +0400 Subject: [PATCH 05/14] fix: failing tests Signed-off-by: Suren Raju --- api/v1alpha1/basic_auth_types.go | 2 +- api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ internal/gatewayapi/testdata/custom-filter-order.out.yaml | 1 - .../testdata/securitypolicy-with-basic-auth.out.yaml | 3 --- internal/ir/xds.go | 2 +- internal/ir/zz_generated.deepcopy.go | 5 +++++ internal/xds/translator/basicauth.go | 4 ++-- 7 files changed, 14 insertions(+), 8 deletions(-) diff --git a/api/v1alpha1/basic_auth_types.go b/api/v1alpha1/basic_auth_types.go index 5ed89b762d..ec12473bd7 100644 --- a/api/v1alpha1/basic_auth_types.go +++ b/api/v1alpha1/basic_auth_types.go @@ -33,5 +33,5 @@ type BasicAuth struct { // If it is not specified, the username will not be forwarded. // // +optional - ForwardUsernameHeader string `json:"forwardUsernameHeader,omitempty"` + ForwardUsernameHeader *string `json:"forwardUsernameHeader,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 97fddd3ff6..bbb7c11455 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -653,6 +653,11 @@ func (in *BackendTrafficPolicySpec) DeepCopy() *BackendTrafficPolicySpec { func (in *BasicAuth) DeepCopyInto(out *BasicAuth) { *out = *in in.Users.DeepCopyInto(&out.Users) + if in.ForwardUsernameHeader != nil { + in, out := &in.ForwardUsernameHeader, &out.ForwardUsernameHeader + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicAuth. diff --git a/internal/gatewayapi/testdata/custom-filter-order.out.yaml b/internal/gatewayapi/testdata/custom-filter-order.out.yaml index a313d440f3..685d6b7fe5 100644 --- a/internal/gatewayapi/testdata/custom-filter-order.out.yaml +++ b/internal/gatewayapi/testdata/custom-filter-order.out.yaml @@ -279,7 +279,6 @@ xdsIR: prefix: /foo security: basicAuth: - forwardUsernameHeader: "" name: securitypolicy/envoy-gateway/policy-for-gateway users: '[redacted]' cors: diff --git a/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml index 52b8d27029..fbfb3c4131 100644 --- a/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml @@ -250,7 +250,6 @@ xdsIR: prefix: /foo1 security: basicAuth: - forwardUsernameHeader: "" name: securitypolicy/default/policy-for-http-route-1 users: '[redacted]' - destination: @@ -276,7 +275,6 @@ xdsIR: prefix: /foo2 security: basicAuth: - forwardUsernameHeader: "" name: securitypolicy/default/policy-for-http-route-1 users: '[redacted]' - destination: @@ -302,7 +300,6 @@ xdsIR: prefix: /bar security: basicAuth: - forwardUsernameHeader: "" name: securitypolicy/default/policy-for-gateway-1 users: '[redacted]' readyListener: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 6243d971c3..9980e99eaf 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -1088,7 +1088,7 @@ type BasicAuth struct { // the backend. The header will be added to the request with the username as the value. // // If it is not specified, the username will not be forwarded. - ForwardUsernameHeader string `json:"forwardUsernameHeader" yaml:"forwardUsernameHeader"` + ForwardUsernameHeader *string `json:"forwardUsernameHeader,omitempty" yaml:"forwardUsernameHeader,omitempty"` } // APIKeyAuth defines the schema for the API Key Authentication. diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 98cf2a9976..6119789062 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -360,6 +360,11 @@ func (in *BasicAuth) DeepCopyInto(out *BasicAuth) { *out = make(PrivateBytes, len(*in)) copy(*out, *in) } + if in.ForwardUsernameHeader != nil { + in, out := &in.ForwardUsernameHeader, &out.ForwardUsernameHeader + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicAuth. diff --git a/internal/xds/translator/basicauth.go b/internal/xds/translator/basicauth.go index 1300135336..502f15e59c 100644 --- a/internal/xds/translator/basicauth.go +++ b/internal/xds/translator/basicauth.go @@ -88,8 +88,8 @@ func buildHCMBasicAuthFilter(basicAuth *ir.BasicAuth) (*hcmv3.HttpFilter, error) }, } // Set the ForwardUsernameHeader field if it is specified. - if basicAuth.ForwardUsernameHeader != "" { - basicAuthProto.ForwardUsernameHeader = basicAuth.ForwardUsernameHeader + if basicAuth.ForwardUsernameHeader != nil && *basicAuth.ForwardUsernameHeader != "" { + basicAuthProto.ForwardUsernameHeader = *basicAuth.ForwardUsernameHeader } if basicAuthAny, err = proto.ToAnyWithValidation(basicAuthProto); err != nil { From 27ad3826ff88b79405008b6605ef629d21d00517 Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Sun, 2 Mar 2025 17:04:50 +0400 Subject: [PATCH 06/14] fix: failing tests Signed-off-by: Suren Raju --- internal/ir/xds_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 3302bef331..cfbd84c913 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -1365,7 +1365,7 @@ func TestRedaction(t *testing.T) { `"name":"","hostname":"","isHTTP2":false,"security":{` + `"oidc":{"name":"","provider":{},"clientID":"","clientSecret":"[redacted]","hmacSecret":"[redacted]"},` + `"apiKeyAuth":{"credentials":{"client-id":"[redacted]"},"extractFrom":null},` + - `"basicAuth":{"name":"","users":"[redacted]","forwardUsernameHeader":""}` + + `"basicAuth":{"name":"","users":"[redacted]"}` + `}}],` + `"isHTTP2":false,"path":{"mergeSlashes":false,"escapedSlashesAction":""}}]}`, }, From c00cdd71e47cc94a3859ce0013589bd16ca11812 Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 08:09:47 +0400 Subject: [PATCH 07/14] Add integration tests Signed-off-by: Suren Raju --- test/e2e/testdata/basic-auth.yaml | 40 +++++++++++++++++++++++++++++++ test/e2e/tests/basic_auth.go | 39 ++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/test/e2e/testdata/basic-auth.yaml b/test/e2e/testdata/basic-auth.yaml index 28f8afc3f0..8910702c7a 100644 --- a/test/e2e/testdata/basic-auth.yaml +++ b/test/e2e/testdata/basic-auth.yaml @@ -93,3 +93,43 @@ spec: basicAuth: users: name: "basic-auth-users-secret-2" +--- +apiVersion: v1 +kind: Secret +metadata: + namespace: gateway-conformance-infra + name: basic-auth-users-secret-3 +data: + .htpasswd: "dXNlcjM6e1NIQX1QcitqQWR4WkdXOFlXVHhGNVJrb2VpTXBkWWs9CnVzZXI0OntTSEF9SC9LemNFcnQ0RTdzdFI1UXltbU8vVkNoTjVzPQ==" +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-with-basic-auth-3 + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: Exact + value: /basic-auth-3 + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: basic-auth-3 + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-with-basic-auth-3 + basicAuth: + users: + name: "basic-auth-users-secret-3" + forwardUsernameHeader: "X-Authenticated-User" diff --git a/test/e2e/tests/basic_auth.go b/test/e2e/tests/basic_auth.go index 298da1c2db..65fc90abf6 100644 --- a/test/e2e/tests/basic_auth.go +++ b/test/e2e/tests/basic_auth.go @@ -209,5 +209,44 @@ var BasicAuthTest = suite.ConformanceTest{ http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) + + // This test verifies that when a user successfully authenticates using BasicAuth, + // their username is forwarded to the backend as a request header. + // The SecurityPolicy "basic-auth-3" is configured with the field "forwardUsernameHeader" set to "X-Authenticated-User". + // The request uses valid credentials (user1:test1), and the test ensures that the response contains + // the expected status code (200) and includes the "X-Authenticated-User" header with the correct username. + t.Run("username forwarded as header", func(t *testing.T) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "http-with-basic-auth-3", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + ancestorRef := gwapiv1a2.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupName), + Kind: gatewayapi.KindPtr(resource.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwapiv1.ObjectName(gwNN.Name), + } + SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "basic-auth-3", Namespace: ns}, suite.ControllerName, ancestorRef) + + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/basic-auth-3", + Headers: map[string]string{ + "Authorization": "Basic dXNlcjE6dGVzdDE=", // user1:test1 + }, + }, + Response: http.Response{ + StatusCode: 200, + Headers: map[string]string{ + "Authorization": "Basic dXNlcjE6dGVzdDE=", + "X-Authenticated-User": "user1", + }, + }, + Namespace: ns, + } + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + }) }, } From 481106c71c5749dd6daa152c2816f1771a0d434d Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 11:12:08 +0400 Subject: [PATCH 08/14] fix: e2e test cases Signed-off-by: Suren Raju --- test/e2e/tests/basic_auth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/basic_auth.go b/test/e2e/tests/basic_auth.go index 65fc90abf6..030e991841 100644 --- a/test/e2e/tests/basic_auth.go +++ b/test/e2e/tests/basic_auth.go @@ -233,14 +233,14 @@ var BasicAuthTest = suite.ConformanceTest{ Request: http.Request{ Path: "/basic-auth-3", Headers: map[string]string{ - "Authorization": "Basic dXNlcjE6dGVzdDE=", // user1:test1 + "Authorization": "Basic dXNlcjQ6dGVzdDQ=", // user4:test4 }, }, Response: http.Response{ StatusCode: 200, Headers: map[string]string{ "Authorization": "Basic dXNlcjE6dGVzdDE=", - "X-Authenticated-User": "user1", + "X-Authenticated-User": "user4", }, }, Namespace: ns, From a194deaa6a25301eb49c51ee35f1a18332ca01a8 Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 11:16:22 +0400 Subject: [PATCH 09/14] Add release note Signed-off-by: Suren Raju --- release-notes/current.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 913c715c04..8a35e4e8a5 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -16,6 +16,7 @@ new features: | Added support for defining Lua EnvoyExtensionPolicies Added RequestID field in ClientTrafficPolicy.HeaderSettings to configure Envoy X-Request-ID behavior. Added support for HorizontalPodAutoscaler to helm chart + Added support for forwarding the authenticated username to the backend via a configurable header in BasicAuth. bug fixes: | Fix traffic splitting when filters are attached to the backendRef. From 0970b0b46b82796b7497057e68f2186759b94d68 Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 11:30:31 +0400 Subject: [PATCH 10/14] fix: pipeline Signed-off-by: Suren Raju --- release-notes/current.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 8a35e4e8a5..3c9d1d5e5c 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -16,7 +16,7 @@ new features: | Added support for defining Lua EnvoyExtensionPolicies Added RequestID field in ClientTrafficPolicy.HeaderSettings to configure Envoy X-Request-ID behavior. Added support for HorizontalPodAutoscaler to helm chart - Added support for forwarding the authenticated username to the backend via a configurable header in BasicAuth. + Added support for forwarding the authenticated username to the backend via a configurable header in BasicAuth bug fixes: | Fix traffic splitting when filters are attached to the backendRef. From 87556c10ec534ec6c169a5b9b98ff98aee0d7bce Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 12:23:07 +0400 Subject: [PATCH 11/14] fix: faling e2e test Signed-off-by: Suren Raju --- test/e2e/tests/basic_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/tests/basic_auth.go b/test/e2e/tests/basic_auth.go index 030e991841..76b9e74f09 100644 --- a/test/e2e/tests/basic_auth.go +++ b/test/e2e/tests/basic_auth.go @@ -239,7 +239,7 @@ var BasicAuthTest = suite.ConformanceTest{ Response: http.Response{ StatusCode: 200, Headers: map[string]string{ - "Authorization": "Basic dXNlcjE6dGVzdDE=", + "Authorization": "Basic dXNlcjQ6dGVzdDQ=", "X-Authenticated-User": "user4", }, }, From 06a61ad74dfc102dbd91bf3cf67b79b8e3bcbbb0 Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 13:42:28 +0400 Subject: [PATCH 12/14] fix: failing e2e tests Signed-off-by: Suren Raju --- test/e2e/tests/basic_auth.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/e2e/tests/basic_auth.go b/test/e2e/tests/basic_auth.go index 76b9e74f09..68e845f265 100644 --- a/test/e2e/tests/basic_auth.go +++ b/test/e2e/tests/basic_auth.go @@ -236,13 +236,18 @@ var BasicAuthTest = suite.ConformanceTest{ "Authorization": "Basic dXNlcjQ6dGVzdDQ=", // user4:test4 }, }, - Response: http.Response{ - StatusCode: 200, - Headers: map[string]string{ - "Authorization": "Basic dXNlcjQ6dGVzdDQ=", - "X-Authenticated-User": "user4", + // Verify that the http header X-Authenticated-User added before sending it to the backend + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/basic-auth-3", + Headers: map[string]string{ + "X-Authenticated-User": "user4", + }, }, }, + Response: http.Response{ + StatusCode: 200 + }, Namespace: ns, } From 4e5f5bff3548cafb3e241feafc2e30cf3d4e2e8b Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 13:45:53 +0400 Subject: [PATCH 13/14] fix: failing tests Signed-off-by: Suren Raju --- test/e2e/tests/basic_auth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/basic_auth.go b/test/e2e/tests/basic_auth.go index 68e845f265..619fca94cb 100644 --- a/test/e2e/tests/basic_auth.go +++ b/test/e2e/tests/basic_auth.go @@ -236,7 +236,7 @@ var BasicAuthTest = suite.ConformanceTest{ "Authorization": "Basic dXNlcjQ6dGVzdDQ=", // user4:test4 }, }, - // Verify that the http header X-Authenticated-User added before sending it to the backend + // Verify that the http header X-Authenticated-User added before sending it to the backend ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/basic-auth-3", @@ -246,7 +246,7 @@ var BasicAuthTest = suite.ConformanceTest{ }, }, Response: http.Response{ - StatusCode: 200 + StatusCode: 200, }, Namespace: ns, } From 78e31053383ed8969a60b324aa4efbd7b317ac4d Mon Sep 17 00:00:00 2001 From: Suren Raju Date: Mon, 3 Mar 2025 15:41:34 +0400 Subject: [PATCH 14/14] fix: test case comment Signed-off-by: Suren Raju --- test/e2e/tests/basic_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/tests/basic_auth.go b/test/e2e/tests/basic_auth.go index 619fca94cb..5045265174 100644 --- a/test/e2e/tests/basic_auth.go +++ b/test/e2e/tests/basic_auth.go @@ -213,7 +213,7 @@ var BasicAuthTest = suite.ConformanceTest{ // This test verifies that when a user successfully authenticates using BasicAuth, // their username is forwarded to the backend as a request header. // The SecurityPolicy "basic-auth-3" is configured with the field "forwardUsernameHeader" set to "X-Authenticated-User". - // The request uses valid credentials (user1:test1), and the test ensures that the response contains + // The request uses valid credentials, and the test ensures that the response contains // the expected status code (200) and includes the "X-Authenticated-User" header with the correct username. t.Run("username forwarded as header", func(t *testing.T) { ns := "gateway-conformance-infra"