diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 16d00920c5..731c31a9d6 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -933,6 +933,18 @@ func buildResponseOverride(policy *egv1a1.BackendTrafficPolicy, resources *resou }, nil } +func checkResponseBodySize(b *string) error { + // Make this configurable in the future + // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto.html#max_direct_response_body_size_bytes + maxDirectResponseSize := 4096 + lenB := len(*b) + if lenB > maxDirectResponseSize { + return fmt.Errorf("response.body size %d greater than the max size %d", lenB, maxDirectResponseSize) + } + + return nil +} + func getCustomResponseBody(body *egv1a1.CustomResponseBody, resources *resource.Resources, policyNs string) (*string, error) { if body != nil && body.Type != nil && *body.Type == egv1a1.ResponseValueTypeValueRef { cm := resources.GetConfigMap(policyNs, string(body.ValueRef.Name)) @@ -940,12 +952,18 @@ func getCustomResponseBody(body *egv1a1.CustomResponseBody, resources *resource. b, dataOk := cm.Data["response.body"] switch { case dataOk: + if err := checkResponseBodySize(&b); err != nil { + return nil, err + } return &b, nil case len(cm.Data) > 0: // Fallback to the first key if response.body is not found for _, value := range cm.Data { b = value break } + if err := checkResponseBodySize(&b); err != nil { + return nil, err + } return &b, nil default: return nil, fmt.Errorf("can't find the key response.body in the referenced configmap %s", body.ValueRef.Name) @@ -955,6 +973,9 @@ func getCustomResponseBody(body *egv1a1.CustomResponseBody, resources *resource. return nil, fmt.Errorf("can't find the referenced configmap %s", body.ValueRef.Name) } } else if body != nil && body.Inline != nil { + if err := checkResponseBodySize(body.Inline); err != nil { + return nil, err + } return body.Inline, nil } diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index 162f169806..702a3f7f30 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -948,7 +948,12 @@ func translateEarlyRequestHeaders(headerModifier *gwapiv1.HTTPHeaderFilter) ([]i } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.ContainsAny(string(addHeader.Name), "/:") { - errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders Filter cannot set headers with a '/' or ':' character in them. Header: %q", string(addHeader.Name))) + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot add a header with a '/' or ':' character in them. Header: '%q'", string(addHeader.Name))) + continue + } + // Gateway API specification allows only valid value as defined by RFC 7230 + if !HeaderValueRegexp.MatchString(addHeader.Value) { + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot add a header with an invalid value. Header: '%q'", string(addHeader.Name))) continue } // Check if the header is a duplicate @@ -988,7 +993,12 @@ func translateEarlyRequestHeaders(headerModifier *gwapiv1.HTTPHeaderFilter) ([]i } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.ContainsAny(string(setHeader.Name), "/:") { - errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set headers with a '/' or ':' character in them. Header: '%s'", string(setHeader.Name))) + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set a header with a '/' or ':' character in them. Header: '%q'", string(setHeader.Name))) + continue + } + // Gateway API specification allows only valid value as defined by RFC 7230 + if !HeaderValueRegexp.MatchString(setHeader.Value) { + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set a header with an invalid value. Header: '%q'", string(setHeader.Name))) continue } diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index 655a133b73..ae5be2196e 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -62,6 +62,9 @@ type HTTPFilterIR struct { ExtensionRefs []*ir.UnstructuredRef } +// Header value pattern according to RFC 7230 +var HeaderValueRegexp = regexp.MustCompile(`^[!-~]+([\t ]?[!-~]+)*$`) + // ProcessHTTPFilters translates gateway api http filters to IRs. func (t *Translator) ProcessHTTPFilters(parentRef *RouteParentContext, route RouteContext, @@ -370,6 +373,7 @@ func (t *Translator) processRequestHeaderModifierFilter( emptyFilterConfig = false } for _, addHeader := range headersToAdd { + emptyFilterConfig = false if addHeader.Name == "" { updateRouteStatusForFilter( @@ -378,16 +382,27 @@ func (t *Translator) processRequestHeaderModifierFilter( // try to process the rest of the headers and produce a valid config. continue } + if !isModifiableHeader(string(addHeader.Name)) { updateRouteStatusForFilter( filterContext, fmt.Sprintf( - "Header: %q. The RequestHeaderModifier filter cannot set the Host header or headers with a '/' "+ + "Header: %q. The RequestHeaderModifier filter cannot add the Host header or headers with a '/' "+ "or ':' character in them. To modify the Host header use the URLRewrite or the HTTPRouteFilter filter.", string(addHeader.Name)), ) continue } + + if !HeaderValueRegexp.MatchString(addHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. RequestHeaderModifier Filter cannot add a header with an invalid value.", + string(addHeader.Name))) + continue + } + // Check if the header is a duplicate headerKey := string(addHeader.Name) canAddHeader := true @@ -437,6 +452,15 @@ func (t *Translator) processRequestHeaderModifierFilter( continue } + if !HeaderValueRegexp.MatchString(setHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. RequestHeaderModifier Filter cannot set a header with an invalid value.", + string(setHeader.Name))) + continue + } + // Check if the header to be set has already been configured headerKey := string(setHeader.Name) canAddHeader := true @@ -550,6 +574,7 @@ func (t *Translator) processResponseHeaderModifierFilter( // try to process the rest of the headers and produce a valid config. continue } + if !isModifiableHeader(string(addHeader.Name)) { updateRouteStatusForFilter( filterContext, @@ -559,6 +584,16 @@ func (t *Translator) processResponseHeaderModifierFilter( string(addHeader.Name))) continue } + + if !HeaderValueRegexp.MatchString(addHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. ResponseHeaderModifier Filter cannot add a header with an invalid value.", + string(addHeader.Name))) + continue + } + // Check if the header is a duplicate headerKey := string(addHeader.Name) canAddHeader := true @@ -607,6 +642,15 @@ func (t *Translator) processResponseHeaderModifierFilter( continue } + if !HeaderValueRegexp.MatchString(setHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. ResponseHeaderModifier Filter cannot set a header with an invalid value.", + string(setHeader.Name))) + continue + } + // Check if the header to be set has already been configured headerKey := string(setHeader.Name) canAddHeader := true diff --git a/internal/gatewayapi/sort.go b/internal/gatewayapi/sort.go index e8042c85e3..bd859d6c0b 100644 --- a/internal/gatewayapi/sort.go +++ b/internal/gatewayapi/sort.go @@ -63,6 +63,7 @@ func (x XdsIRRoutes) Less(i, j int) bool { // Equal case // 3. Sort based on the number of Header matches. + // When the number is same, sort based on number of Exact Header matches. hCountI := len(x[i].HeaderMatches) hCountJ := len(x[j].HeaderMatches) if hCountI < hCountJ { @@ -71,12 +72,31 @@ func (x XdsIRRoutes) Less(i, j int) bool { if hCountI > hCountJ { return false } + + hExtNumberI := numberOfExactMatches(x[i].HeaderMatches) + hExtNumberJ := numberOfExactMatches(x[j].HeaderMatches) + if hExtNumberI < hExtNumberJ { + return true + } + if hExtNumberI > hExtNumberJ { + return false + } // Equal case // 4. Sort based on the number of Query param matches. + // When the number is same, sort based on number of Exact Query param matches. qCountI := len(x[i].QueryParamMatches) qCountJ := len(x[j].QueryParamMatches) - return qCountI < qCountJ + if qCountI < qCountJ { + return true + } + if qCountI > qCountJ { + return false + } + + qExtNumberI := numberOfExactMatches(x[i].QueryParamMatches) + qExtNumberJ := numberOfExactMatches(x[j].QueryParamMatches) + return qExtNumberI < qExtNumberJ } // sortXdsIR sorts the xdsIR based on the match precedence @@ -107,3 +127,13 @@ func pathMatchCount(pathMatch *ir.StringMatch) int { } return 0 } + +func numberOfExactMatches(stringMatches []*ir.StringMatch) int { + var cnt int + for _, stringMatch := range stringMatches { + if stringMatch != nil && stringMatch.Exact != nil { + cnt++ + } + } + return cnt +} diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml index 3b2331bba7..5fb1efb474 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml @@ -13,13 +13,18 @@ clientTrafficPolicies: add: - name: "" value: "empty" - - name: "invalid" + - name: "Example/Header/1" value: ":/" + - name: "example-header-2" + value: | + multi-line-header-value set: - name: "" value: "empty" - - name: "invalid" + - name: "Example:Header:3" value: ":/" + - name: "example-header-4" + value: " invalid" remove: - "" targetRef: diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml index 18e9349235..04357f119f 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml @@ -11,15 +11,20 @@ clientTrafficPolicies: add: - name: "" value: empty - - name: invalid + - name: Example/Header/1 value: :/ + - name: example-header-2 + value: | + multi-line-header-value remove: - "" set: - name: "" value: empty - - name: invalid + - name: Example:Header:3 value: :/ + - name: example-header-4 + value: ' invalid' enableEnvoyHeaders: true preserveXRequestID: true withUnderscoresAction: Allow @@ -38,8 +43,13 @@ clientTrafficPolicies: - lastTransitionTime: null message: |- Headers: EarlyRequestHeaders cannot add a header with an empty name + EarlyRequestHeaders cannot add a header with a '/' or ':' character in them. Header: '"Example/Header/1"' + EarlyRequestHeaders cannot add a header with an invalid value. Header: '"example-header-2"' EarlyRequestHeaders cannot set a header with an empty name - EarlyRequestHeaders cannot remove a header with an empty name. + EarlyRequestHeaders cannot set a header with a '/' or ':' character in them. Header: '"Example:Header:3"' + EarlyRequestHeaders cannot set a header with an invalid value. Header: '"example-header-4"' + EarlyRequestHeaders cannot remove a header with an empty name + EarlyRequestHeaders did not provide valid configuration to add/set/remove any headers. reason: Invalid status: "False" type: Accepted diff --git a/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml b/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml index bd9a316227..29f5b5c7f3 100644 --- a/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml @@ -49,7 +49,7 @@ httpRoutes: - apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: direct-response-with-errors + name: direct-response-with-value-not-found namespace: default spec: parentRefs: @@ -67,6 +67,27 @@ httpRoutes: group: gateway.envoyproxy.io kind: HTTPRouteFilter name: direct-response-value-ref-not-found +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: direct-response-too-long + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: /too-long + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-too-long configMaps: - apiVersion: v1 kind: ConfigMap @@ -117,3 +138,14 @@ httpFilters: group: "" kind: ConfigMap name: value-ref-response +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: direct-response-too-long + namespace: default + spec: + directResponse: + contentType: text/plain + body: + type: Inline + inline: "-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------" diff --git a/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml b/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml index ad03c207f8..d764d9648a 100644 --- a/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml @@ -17,7 +17,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 2 + - attachedRoutes: 3 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -95,7 +95,7 @@ httpRoutes: kind: HTTPRoute metadata: creationTimestamp: null - name: direct-response-with-errors + name: direct-response-with-value-not-found namespace: default spec: parentRefs: @@ -131,6 +131,47 @@ httpRoutes: name: gateway-1 namespace: envoy-gateway sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: direct-response-too-long + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-too-long + type: ExtensionRef + matches: + - path: + type: PathPrefix + value: /too-long + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Invalid filter HTTPRouteFilter: response.body size 4097 greater + than the max size 4096' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http infraIR: envoy-gateway/gateway-1: proxy: diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml index c965d32968..0037ee5543 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml @@ -39,7 +39,7 @@ httpRoutes: requestHeaderModifier: set: - name: "example-header-1" - value: "" + value: "dummy" add: - name: "example-header-2" value: "" diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml index 38d1a56ca6..3a2cc9dbe7 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml @@ -65,7 +65,7 @@ httpRoutes: value: "" set: - name: example-header-1 - value: "" + value: dummy type: RequestHeaderModifier matches: - path: @@ -74,9 +74,10 @@ httpRoutes: parents: - conditions: - lastTransitionTime: null - message: Route is accepted - reason: Accepted - status: "True" + message: 'Header: "example-header-2". RequestHeaderModifier Filter cannot + add a header with an invalid value.' + reason: UnsupportedValue + status: "False" type: Accepted - lastTransitionTime: null message: Resolved all the Object references for the Route @@ -124,33 +125,3 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 - routes: - - addRequestHeaders: - - append: true - name: example-header-2 - value: - - "" - - append: false - name: example-header-1 - value: - - "" - destination: - name: httproute/default/httproute-1/rule/0 - settings: - - addressType: IP - endpoints: - - host: 7.7.7.7 - port: 8080 - protocol: HTTP - weight: 1 - hostname: gateway.envoyproxy.io - isHTTP2: false - metadata: - kind: HTTPRoute - name: httproute-1 - namespace: default - name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io - pathMatch: - distinct: false - name: "" - prefix: / diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml new file mode 100644 index 0000000000..75d1c1669d --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml @@ -0,0 +1,47 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + set: + - name: "example-header-1" + value: | + multi-line-header-value + add: + - name: "example-header-2" + value: "dummy" + diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml new file mode 100644 index 0000000000..17081c3ca0 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml @@ -0,0 +1,128 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - requestHeaderModifier: + add: + - name: example-header-2 + value: dummy + set: + - name: example-header-1 + value: | + multi-line-header-value + type: RequestHeaderModifier + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example-header-1". RequestHeaderModifier Filter cannot + set a header with an invalid value.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml new file mode 100644 index 0000000000..7223aef583 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml @@ -0,0 +1,68 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: RegularExpression + name: x-org-id + value: '.*' + - type: RegularExpression + name: hostname + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: Exact + name: x-org-id + value: test-org + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml new file mode 100644 index 0000000000..b568148765 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml @@ -0,0 +1,220 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: RegularExpression + value: .* + - name: hostname + type: RegularExpression + value: .* + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: Exact + value: test-org + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + name: x-org-id + safeRegex: .* + - distinct: false + name: hostname + safeRegex: .* + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + exact: test-org + name: x-org-id + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml new file mode 100644 index 0000000000..06ae961aee --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml @@ -0,0 +1,65 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: RegularExpression + name: x-org-id + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: Exact + name: x-org-id + value: test-org + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml new file mode 100644 index 0000000000..ccd6eebe66 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml @@ -0,0 +1,214 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: RegularExpression + value: .* + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: Exact + value: test-org + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + exact: test-org + name: x-org-id + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + name: x-org-id + safeRegex: .* + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml new file mode 100644 index 0000000000..adc40a7ed7 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml @@ -0,0 +1,68 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: RegularExpression + name: id + value: '.*' + - type: RegularExpression + name: name + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: Exact + name: id + value: 1234 + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml new file mode 100644 index 0000000000..3c2e62fa5e --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml @@ -0,0 +1,220 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: RegularExpression + value: .* + - name: name + type: RegularExpression + value: .* + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: Exact + value: "1234" + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + name: id + safeRegex: .* + - distinct: false + name: name + safeRegex: .* + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + exact: "1234" + name: id diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml new file mode 100644 index 0000000000..128cefe8cd --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml @@ -0,0 +1,65 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: RegularExpression + name: id + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: Exact + name: id + value: 1234 + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml new file mode 100644 index 0000000000..4475888d5c --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml @@ -0,0 +1,214 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: RegularExpression + value: .* + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: Exact + value: "1234" + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + exact: "1234" + name: id + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + name: id + safeRegex: .* diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml index 1c0016800d..b84f703b65 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml @@ -39,8 +39,7 @@ httpRoutes: responseHeaderModifier: set: - name: "example-header-1" - value: "" + value: "dummy" add: - name: "example-header-2" value: "" - diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml index e0cf97da28..7d4e91fa7e 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml @@ -65,7 +65,7 @@ httpRoutes: value: "" set: - name: example-header-1 - value: "" + value: dummy type: ResponseHeaderModifier matches: - path: @@ -74,9 +74,10 @@ httpRoutes: parents: - conditions: - lastTransitionTime: null - message: Route is accepted - reason: Accepted - status: "True" + message: 'Header: "example-header-2". ResponseHeaderModifier Filter cannot + add a header with an invalid value.' + reason: UnsupportedValue + status: "False" type: Accepted - lastTransitionTime: null message: Resolved all the Object references for the Route @@ -124,33 +125,3 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 - routes: - - addResponseHeaders: - - append: true - name: example-header-2 - value: - - "" - - append: false - name: example-header-1 - value: - - "" - destination: - name: httproute/default/httproute-1/rule/0 - settings: - - addressType: IP - endpoints: - - host: 7.7.7.7 - port: 8080 - protocol: HTTP - weight: 1 - hostname: gateway.envoyproxy.io - isHTTP2: false - metadata: - kind: HTTPRoute - name: httproute-1 - namespace: default - name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io - pathMatch: - distinct: false - name: "" - prefix: / diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml new file mode 100644 index 0000000000..802391150f --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml @@ -0,0 +1,46 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: "example-header-1" + value: | + multi-line-header-value + add: + - name: "example-header-2" + value: "dummy" diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml new file mode 100644 index 0000000000..913c4adcc6 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml @@ -0,0 +1,128 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - responseHeaderModifier: + add: + - name: example-header-2 + value: dummy + set: + - name: example-header-1 + value: | + multi-line-header-value + type: ResponseHeaderModifier + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example-header-1". ResponseHeaderModifier Filter cannot + set a header with an invalid value.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index d655e8f088..c8cd5e444c 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -215,63 +215,72 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques string(gwapiv1.GatewayClassReasonInvalidParameters), msg) r.resources.GatewayClassStatuses.Store(utils.NamespacedName(gc), &gc.Status) - return reconcile.Result{}, nil + continue } } // Add all Gateways, their associated Routes, and referenced resources to the resourceTree if err = r.processGateways(ctx, managedGC, resourceMappings, gwcResource); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processGateways for gatewayClass %s, skipping it", managedGC.Name)) + continue } if r.eppCRDExists { // Add all EnvoyPatchPolicies to the resourceTree if err = r.processEnvoyPatchPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processEnvoyPatchPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.ctpCRDExists { // Add all ClientTrafficPolicies and their referenced resources to the resourceTree if err = r.processClientTrafficPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processClientTrafficPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.btpCRDExists { // Add all BackendTrafficPolicies to the resourceTree if err = r.processBackendTrafficPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processBackendTrafficPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.spCRDExists { // Add all SecurityPolicies and their referenced resources to the resourceTree if err = r.processSecurityPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processSecurityPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.bTLSPolicyCRDExists { // Add all BackendTLSPolies to the resourceTree if err = r.processBackendTLSPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processBackendTLSPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.eepCRDExists { // Add all EnvoyExtensionPolicies and their referenced resources to the resourceTree if err = r.processEnvoyExtensionPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processEnvoyExtensionPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if err = r.processExtensionServerPolicies(ctx, gwcResource); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processExtensionServerPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } if r.backendCRDExists { if err = r.processBackends(ctx, gwcResource); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processBackends for gatewayClass %s, skipping it", managedGC.Name)) + continue } } @@ -287,9 +296,9 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques if err != nil { r.log.Error(err, "unable to find the namespace") if kerrors.IsNotFound(err) { - return reconcile.Result{}, nil + continue } - return reconcile.Result{}, err + continue } gwcResource.Namespaces = append(gwcResource.Namespaces, namespace) @@ -313,20 +322,20 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques r.resources.GatewayClassStatuses.Store(utils.NamespacedName(gc), &gc.Status) if len(gwcResource.Gateways) == 0 { - r.log.Info("No gateways found for accepted gatewayclass") + r.log.Info("No gateways found for accepted gatewayClass") // If needed, remove the finalizer from the accepted GatewayClass. if err := r.removeFinalizer(ctx, managedGC); err != nil { - r.log.Error(err, fmt.Sprintf("failed to remove finalizer from gatewayclass %s", + r.log.Error(err, fmt.Sprintf("failed to remove finalizer from gatewayClass %s", managedGC.Name)) - return reconcile.Result{}, err + continue } } else { // finalize the accepted GatewayClass. if err := r.addFinalizer(ctx, managedGC); err != nil { - r.log.Error(err, fmt.Sprintf("failed adding finalizer to gatewayclass %s", + r.log.Error(err, fmt.Sprintf("failed adding finalizer to gatewayClass %s", managedGC.Name)) - return reconcile.Result{}, err + continue } } } @@ -552,60 +561,92 @@ func (r *gatewayAPIReconciler) processSecurityPolicyObjectRefs( // Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing extAuth := policy.Spec.ExtAuth if extAuth != nil { - var backendRef *gwapiv1.BackendObjectReference + var backendRef gwapiv1.BackendObjectReference if extAuth.GRPC != nil { - backendRef = extAuth.GRPC.BackendRef + if extAuth.GRPC.BackendRef != nil { + backendRef = *extAuth.GRPC.BackendRef + } if len(extAuth.GRPC.BackendRefs) > 0 { if len(extAuth.GRPC.BackendRefs) != 0 { - backendRef = egv1a1.ToBackendObjectReference(extAuth.GRPC.BackendRefs[0]) + backendRef = extAuth.GRPC.BackendRefs[0].BackendObjectReference } } - } else { - backendRef = extAuth.HTTP.BackendRef + } else if extAuth.HTTP != nil { + if extAuth.HTTP.BackendRef != nil { + backendRef = *extAuth.HTTP.BackendRef + } if len(extAuth.HTTP.BackendRefs) > 0 { if len(extAuth.HTTP.BackendRefs) != 0 { - backendRef = egv1a1.ToBackendObjectReference(extAuth.HTTP.BackendRefs[0]) + backendRef = extAuth.HTTP.BackendRefs[0].BackendObjectReference } } } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindSecurityPolicy, + policy.Namespace, + policy.Name, + backendRef); err != nil { + r.log.Error(err, + "failed to process ExtAuth BackendRef for SecurityPolicy", + "policy", policy, "backendRef", backendRef) + } + } - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) + } +} - if backendNamespace != policy.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindSecurityPolicy, - namespace: policy.Namespace, - name: policy.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } +// processBackendRef adds the referenced BackendRef to the resourceMap for later processBackendRefs. +// If BackendRef exists in a different namespace and there is a ReferenceGrant, adds ReferenceGrant to the resourceTree. +func (r *gatewayAPIReconciler) processBackendRef( + ctx context.Context, + resourceMap *resourceMappings, + resourceTree *resource.Resources, + ownerKind string, + ownerNS string, + ownerName string, + backendRef gwapiv1.BackendObjectReference, +) error { + backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, ownerNS) + resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ + Group: backendRef.Group, + Kind: backendRef.Kind, + Namespace: gatewayapi.NamespacePtr(backendNamespace), + Name: backendRef.Name, + }) + + if backendNamespace != ownerNS { + from := ObjectKindNamespacedName{ + kind: ownerKind, + namespace: ownerNS, + name: ownerName, + } + to := ObjectKindNamespacedName{ + kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), + namespace: backendNamespace, + name: string(backendRef.Name), + } + refGrant, err := r.findReferenceGrant(ctx, from, to) + switch { + case err != nil: + return fmt.Errorf("failed to find ReferenceGrant: %w", err) + case refGrant == nil: + return fmt.Errorf( + "no matching ReferenceGrants found: from %s/%s to %s/%s", + from.kind, from.namespace, to.kind, to.namespace) + default: + // RefGrant found + if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { + resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) + resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) + r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, + "name", refGrant.Name) } } } + return nil } // processOIDCHMACSecret adds the OIDC HMAC Secret to the resourceTree. @@ -920,7 +961,7 @@ func (r *gatewayAPIReconciler) processGateways(ctx context.Context, managedGC *g if err := r.client.List(ctx, gatewayList, &client.ListOptions{ FieldSelector: fields.OneTermEqualSelector(classGatewayIndex, managedGC.Name), }); err != nil { - r.log.Info("no associated Gateways found for GatewayClass", "name", managedGC.Name) + r.log.Error(err, "failed to list gateways for gatewayClass %s", managedGC.Name) return err } @@ -2115,42 +2156,17 @@ func (r *gatewayAPIReconciler) processEnvoyExtensionPolicyObjectRefs( // Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing for _, ep := range policy.Spec.ExtProc { for _, br := range ep.BackendRefs { - backendRef := br.BackendObjectReference - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != policy.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindEnvoyExtensionPolicy, - namespace: policy.Namespace, - name: policy.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindEnvoyExtensionPolicy, + policy.Namespace, + policy.Name, + br.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process ExtProc BackendRef for EnvoyExtensionPolicy", + "policy", policy, "backendRef", br.BackendObjectReference) } } } diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go index d008e7b2f7..d089746cdf 100644 --- a/internal/provider/kubernetes/controller_test.go +++ b/internal/provider/kubernetes/controller_test.go @@ -421,15 +421,10 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) { objs := []client.Object{tc.envoyExtensionPolicy, tc.backend, tc.referenceGrant} // Create the reconciler. - logger := logging.DefaultLogger(egv1a1.LogLevelInfo) + r := setupReferenceGrantReconciler(objs) ctx := context.Background() - r := &gatewayAPIReconciler{ - log: logger, - classController: "some-gateway-class", - } - r.client = fakeclient.NewClientBuilder(). WithScheme(envoygateway.GetScheme()). WithObjects(objs...). @@ -449,3 +444,380 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) { }) } } + +func TestProcessSecurityPolicyObjectRefs(t *testing.T) { + testCases := []struct { + name string + securityPolicy *egv1a1.SecurityPolicy + backend *egv1a1.Backend + referenceGrant *gwapiv1b1.ReferenceGrant + shouldBeAdded bool + }{ + { + name: "valid security policy with extAuth grpc proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth grpc proper ref grant to backend (deprecated field)", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRef: &gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth grpc wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + { + name: "valid security policy with extAuth http proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth http proper ref grant to backend (deprecated field)", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRef: &gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth http wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + } + + for i := range testCases { + tc := testCases[i] + // Run the test cases. + t.Run(tc.name, func(t *testing.T) { + // Add objects referenced by test cases. + objs := []client.Object{tc.securityPolicy, tc.backend, tc.referenceGrant} + r := setupReferenceGrantReconciler(objs) + + ctx := context.Background() + resourceTree := resource.NewResources() + resourceMap := newResourceMapping() + + err := r.processSecurityPolicies(ctx, resourceTree, resourceMap) + require.NoError(t, err) + if tc.shouldBeAdded { + require.Contains(t, resourceTree.ReferenceGrants, tc.referenceGrant) + } else { + require.NotContains(t, resourceTree.ReferenceGrants, tc.referenceGrant) + } + }) + } +} + +func setupReferenceGrantReconciler(objs []client.Object) *gatewayAPIReconciler { + logger := logging.DefaultLogger(egv1a1.LogLevelInfo) + + r := &gatewayAPIReconciler{ + log: logger, + classController: "some-gateway-class", + } + + r.client = fakeclient.NewClientBuilder(). + WithScheme(envoygateway.GetScheme()). + WithObjects(objs...). + WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc()). + Build() + return r +} diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index 9866f6d73a..62f0c2b3bf 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -582,35 +582,47 @@ func secretSecurityPolicyIndexFunc(rawObj client.Object) []string { func backendSecurityPolicyIndexFunc(rawObj client.Object) []string { securityPolicy := rawObj.(*egv1a1.SecurityPolicy) - var backendRef *gwapiv1.BackendObjectReference + var ( + backendRefs []gwapiv1.BackendObjectReference + values []string + ) if securityPolicy.Spec.ExtAuth != nil { if securityPolicy.Spec.ExtAuth.HTTP != nil { http := securityPolicy.Spec.ExtAuth.HTTP - backendRef = http.BackendRef + if http.BackendRef != nil { + backendRefs = append(backendRefs, *http.BackendRef) + } if len(http.BackendRefs) > 0 { - backendRef = egv1a1.ToBackendObjectReference(http.BackendRefs[0]) + backendRefs = append(backendRefs, http.BackendRefs[0].BackendObjectReference) } } else if securityPolicy.Spec.ExtAuth.GRPC != nil { grpc := securityPolicy.Spec.ExtAuth.GRPC - backendRef = grpc.BackendRef + if grpc.BackendRef != nil { + backendRefs = append(backendRefs, *grpc.BackendRef) + } if len(grpc.BackendRefs) > 0 { - backendRef = egv1a1.ToBackendObjectReference(grpc.BackendRefs[0]) + backendRefs = append(backendRefs, grpc.BackendRefs[0].BackendObjectReference) + } + } + } + if securityPolicy.Spec.JWT != nil { + for _, provider := range securityPolicy.Spec.JWT.Providers { + for _, backendRef := range provider.RemoteJWKS.BackendRefs { + backendRefs = append(backendRefs, backendRef.BackendObjectReference) } } } - if backendRef != nil { - return []string{ + for _, reference := range backendRefs { + values = append(values, types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(backendRef.Namespace, securityPolicy.Namespace), - Name: string(backendRef.Name), + Namespace: gatewayapi.NamespaceDerefOr(reference.Namespace, securityPolicy.Namespace), + Name: string(reference.Name), }.String(), - } + ) } - - // This should not happen because the CEL validation should catch it. - return []string{} + return values } // addCtpIndexers adds indexing on ClientTrafficPolicy, for ConfigMap or Secret objects that are diff --git a/internal/provider/kubernetes/kubernetes.go b/internal/provider/kubernetes/kubernetes.go index fb42b76d44..2beadfd287 100644 --- a/internal/provider/kubernetes/kubernetes.go +++ b/internal/provider/kubernetes/kubernetes.go @@ -96,7 +96,7 @@ func New(ctx context.Context, restCfg *rest.Config, svrCfg *ec.Server, resources // Create and register the controllers with the manager. if err := newGatewayAPIController(ctx, mgr, svrCfg, updateHandler.Writer(), resources); err != nil { - return nil, fmt.Errorf("failted to create gatewayapi controller: %w", err) + return nil, fmt.Errorf("failed to create gatewayapi controller: %w", err) } // Add health check health probes. diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go index 1dd2afe5ea..c2891b21ec 100644 --- a/internal/provider/kubernetes/routes.go +++ b/internal/provider/kubernetes/routes.go @@ -60,34 +60,17 @@ func (r *gatewayAPIReconciler) processTLSRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tlsRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != tlsRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindTLSRoute, namespace: tlsRoute.Namespace, name: tlsRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindTLSRoute, + tlsRoute.Namespace, + tlsRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for TLSRoute", + "tlsRoute", tlsRoute, "backendRef", backendRef.BackendObjectReference) } } } @@ -142,42 +125,17 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, grpcRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != grpcRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindGRPCRoute, - namespace: grpcRoute.Namespace, - name: grpcRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindGRPCRoute, + grpcRoute.Namespace, + grpcRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for GRPCRoute", + "grpcRoute", grpcRoute, "backendRef", backendRef.BackendObjectReference) } } @@ -280,42 +238,17 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, httpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != httpRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindHTTPRoute, - namespace: httpRoute.Namespace, - name: httpRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindHTTPRoute, + httpRoute.Namespace, + httpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for HTTPRoute", + "httpRoute", httpRoute, "backendRef", backendRef.BackendObjectReference) } } @@ -351,42 +284,17 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(mirrorBackendRef.Namespace, httpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: mirrorBackendRef.BackendObjectReference.Group, - Kind: mirrorBackendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: mirrorBackendRef.Name, - }) - - if backendNamespace != httpRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindHTTPRoute, - namespace: httpRoute.Namespace, - name: httpRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(mirrorBackendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(mirrorBackendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindHTTPRoute, + httpRoute.Namespace, + httpRoute.Name, + mirrorBackendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for HTTPRouteFilter", + "httpRoute", httpRoute, "backendRef", mirrorBackendRef.BackendObjectReference) } } else if filter.Type == gwapiv1.HTTPRouteFilterExtensionRef { // NOTE: filters must be in the same namespace as the HTTPRoute @@ -485,34 +393,17 @@ func (r *gatewayAPIReconciler) processTCPRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tcpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != tcpRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindTCPRoute, namespace: tcpRoute.Namespace, name: tcpRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindTCPRoute, + tcpRoute.Namespace, + tcpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for TCPRoute", + "tcpRoute", tcpRoute, "backendRef", backendRef.BackendObjectReference) } } } @@ -566,33 +457,17 @@ func (r *gatewayAPIReconciler) processUDPRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, udpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != udpRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindUDPRoute, namespace: udpRoute.Namespace, name: udpRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindUDPRoute, + udpRoute.Namespace, + udpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for UDPRoute", + "udpRoute", udpRoute, "backendRef", backendRef.BackendObjectReference) } } } diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 66e779b4e3..4d95393f8c 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -495,6 +495,16 @@ func buildEarlyHeaderMutation(headers *ir.HeaderSettings) []*corev3.TypedExtensi } func addServerNamesMatch(xdsListener *listenerv3.Listener, filterChain *listenerv3.FilterChain, hostnames []string) error { + // Skip adding ServerNames match for: + // 1. nil listeners + // 2. UDP (QUIC) listeners used for HTTP3 + // 3. wildcard hostnames + if xdsListener == nil || (xdsListener.GetAddress() != nil && + xdsListener.GetAddress().GetSocketAddress() != nil && + xdsListener.GetAddress().GetSocketAddress().GetProtocol() == corev3.SocketAddress_UDP) { + return nil + } + // Dont add a filter chain match if the hostname is a wildcard character. if len(hostnames) > 0 && hostnames[0] != "*" { filterChain.FilterChainMatch = &listenerv3.FilterChainMatch{ diff --git a/internal/xds/translator/server_names_match_test.go b/internal/xds/translator/server_names_match_test.go new file mode 100644 index 0000000000..ce2f8b108b --- /dev/null +++ b/internal/xds/translator/server_names_match_test.go @@ -0,0 +1,134 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package translator + +import ( + "testing" + + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" + "github.com/stretchr/testify/require" +) + +func TestAddServerNamesMatch(t *testing.T) { + tests := []struct { + name string + xdsListener *listenerv3.Listener + hostnames []string + expectFilterChain bool + expectTLSInspector bool + expectServerNames []string + }{ + { + name: "nil listener", + xdsListener: nil, + hostnames: []string{"example.com"}, + expectFilterChain: false, + expectTLSInspector: false, + expectServerNames: nil, + }, + { + name: "UDP (QUIC) listener for HTTP3", + xdsListener: &listenerv3.Listener{ + Address: &corev3.Address{ + Address: &corev3.Address_SocketAddress{ + SocketAddress: &corev3.SocketAddress{ + Protocol: corev3.SocketAddress_UDP, + Address: "0.0.0.0", + PortSpecifier: &corev3.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + }, + hostnames: []string{"example.com"}, + expectFilterChain: false, + expectTLSInspector: false, + expectServerNames: nil, + }, + { + name: "TCP listener with non-wildcard hostnames", + xdsListener: &listenerv3.Listener{ + Address: &corev3.Address{ + Address: &corev3.Address_SocketAddress{ + SocketAddress: &corev3.SocketAddress{ + Protocol: corev3.SocketAddress_TCP, + Address: "0.0.0.0", + PortSpecifier: &corev3.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + }, + hostnames: []string{"example.com", "api.example.com"}, + expectFilterChain: true, + expectTLSInspector: true, + expectServerNames: []string{"example.com", "api.example.com"}, + }, + { + name: "TCP listener with wildcard hostname", + xdsListener: &listenerv3.Listener{ + Address: &corev3.Address{ + Address: &corev3.Address_SocketAddress{ + SocketAddress: &corev3.SocketAddress{ + Protocol: corev3.SocketAddress_TCP, + Address: "0.0.0.0", + PortSpecifier: &corev3.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + }, + hostnames: []string{"*"}, + expectFilterChain: false, + expectTLSInspector: false, + expectServerNames: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filterChain := &listenerv3.FilterChain{} + + err := addServerNamesMatch(tt.xdsListener, filterChain, tt.hostnames) + require.NoError(t, err) + + // Check if filter chain match was added + if tt.expectFilterChain { + require.NotNil(t, filterChain.FilterChainMatch) + require.Equal(t, tt.expectServerNames, filterChain.FilterChainMatch.ServerNames) + } else { + require.Nil(t, filterChain.FilterChainMatch) + } + + // Check if TLS inspector was added + if tt.xdsListener != nil && tt.expectTLSInspector { + hasTLSInspector := false + for _, filter := range tt.xdsListener.ListenerFilters { + if filter.Name == wellknown.TlsInspector { + hasTLSInspector = true + break + } + } + require.True(t, hasTLSInspector, "TLS inspector filter should be added") + } else if tt.xdsListener != nil { + // For non-nil listeners that shouldn't have TLS inspector + hasTLSInspector := false + for _, filter := range tt.xdsListener.ListenerFilters { + if filter.Name == wellknown.TlsInspector { + hasTLSInspector = true + break + } + } + require.False(t, hasTLSInspector, "TLS inspector filter should not be added") + } + }) + } +} diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 6c69e1673e..d71df8ec89 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -10,22 +10,18 @@ security updates: | new features: | bug fixes: | - Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode. - Fixed updates of endpoints when mirrored backend Pod IPs are changed. - Fix not logging an error and returning it in the K8s Reconcile method when a GatewayClass is not accepted. - Fix allowing empty text field for opentelemetry sink when using JSON format. - Fixed validation of host header in RequestHeaderModifier filter. - Retrigger reconciliation when backendRef of type ServiceImport is updated or when EndpointSlice(s) for a ServiceImport are updated. + Fix reference grant from SecurityPolicy to referenced remoteJWKS backend not respected. + Fix httproute precedence by considering header/query match type. + Fix return err if direct response size exceeds limit. + Fix do not add tls inspector filter to quic listener. + Fix process remaining gatewayClasses after encountering an err. + Added validation for header values. # Enhancements that improve performance. performance improvements: | - Added a cache for the Wasm OCI image permission checks and check the pullSecrets against the OCI image registry in - a background goroutine. # Deprecated features or APIs. deprecations: | # Other notable changes not covered by the above sections. Other changes: | - Bumped envoy to v1.33.1. - Bumpled ratelimit to 0141a24f.