From fbc7945985085783e27cc8a445a4cc1dbb3b97b3 Mon Sep 17 00:00:00 2001 From: danehans Date: Tue, 13 Dec 2022 11:10:01 -0800 Subject: [PATCH] Adds AuthenFilter Support to Resource Translator Signed-off-by: danehans --- internal/gatewayapi/filters.go | 61 ++++++++--- internal/gatewayapi/route.go | 2 +- ...with-non-existent-authenfilter-ref.in.yaml | 43 ++++++++ ...ith-non-existent-authenfilter-ref.out.yaml | 102 ++++++++++++++++++ ...direct-filter-invalid-filter-type.out.yaml | 4 +- .../httproute-with-valid-authenfilter.in.yaml | 55 ++++++++++ ...httproute-with-valid-authenfilter.out.yaml | 101 +++++++++++++++++ 7 files changed, 350 insertions(+), 18 deletions(-) create mode 100644 internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.out.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-valid-authenfilter.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-valid-authenfilter.out.yaml diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index a8bf5753c9..c81073da90 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -25,7 +25,7 @@ type HTTPFiltersTranslator interface { processRedirectFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext) processRequestHeaderModifierFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext) processResponseHeaderModifierFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext) - processUnknownHTTPFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext) + processExtensionRefHTTPFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext, resources *Resources) processUnsupportedHTTPFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext) } @@ -52,7 +52,10 @@ type HTTPFilterChains struct { } // ProcessHTTPFilters translate gateway api http filters to IRs. -func (t *Translator) ProcessHTTPFilters(parentRef *RouteParentContext, httpRoute *HTTPRouteContext, filters []v1beta1.HTTPRouteFilter) *HTTPFiltersContext { +func (t *Translator) ProcessHTTPFilters(parentRef *RouteParentContext, + httpRoute *HTTPRouteContext, + filters []v1beta1.HTTPRouteFilter, + resources *Resources) *HTTPFiltersContext { httpFiltersContext := &HTTPFiltersContext{ ParentRef: parentRef, HTTPRoute: httpRoute, @@ -60,11 +63,16 @@ func (t *Translator) ProcessHTTPFilters(parentRef *RouteParentContext, httpRoute HTTPFilterChains: &HTTPFilterChains{}, } - for _, filter := range filters { + for i := range filters { + filter := filters[i] // If an invalid filter type has been configured then skip processing any more filters if httpFiltersContext.DirectResponse != nil { break } + if err := ValidateHTTPRouteFilter(&filter); err != nil { + t.processInvalidHTTPFilter(filter, httpFiltersContext, err) + break + } switch filter.Type { case v1beta1.HTTPRouteFilterURLRewrite: @@ -76,7 +84,7 @@ func (t *Translator) ProcessHTTPFilters(parentRef *RouteParentContext, httpRoute case v1beta1.HTTPRouteFilterResponseHeaderModifier: t.processResponseHeaderModifierFilter(filter, httpFiltersContext) case v1beta1.HTTPRouteFilterExtensionRef: - t.processUnknownHTTPFilter(filter, httpFiltersContext) + t.processExtensionRefHTTPFilter(filter, httpFiltersContext, resources) default: t.processUnsupportedHTTPFilter(filter, httpFiltersContext) } @@ -599,12 +607,38 @@ func (t *Translator) processResponseHeaderModifierFilter( } } -func (t *Translator) processUnknownHTTPFilter( - filter v1beta1.HTTPRouteFilter, - filterContext *HTTPFiltersContext) { - // "If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. - // Instead, requests that would have been processed by that filter MUST receive a HTTP error response." - errMsg := fmt.Sprintf("Unknown custom filter type: %s", filter.Type) +func (t *Translator) processExtensionRefHTTPFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext, resources *Resources) { + // If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. + // Instead, requests that would have been processed by that filter MUST receive an HTTP error + // response. + found := false + + for _, f := range resources.AuthenFilters { + if f.Namespace == filterContext.HTTPRoute.Namespace && f.Name == string(filter.ExtensionRef.Name) { + found = true + break + } + } + + if !found { + errMsg := fmt.Sprintf("Reference not found for filter type: %s", filter.Type) + // Reset the conditions in case the Accepted=True condition exists. + filterContext.ParentRef.ResetConditions(filterContext.HTTPRoute) + filterContext.ParentRef.SetCondition(filterContext.HTTPRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + v1beta1.RouteReasonBackendNotFound, + errMsg, + ) + filterContext.DirectResponse = &ir.DirectResponse{ + Body: &errMsg, + StatusCode: 500, + } + } +} + +func (t *Translator) processUnsupportedHTTPFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext) { + errMsg := fmt.Sprintf("Unsupported filter type: %s", filter.Type) filterContext.ParentRef.SetCondition(filterContext.HTTPRoute, v1beta1.RouteConditionAccepted, metav1.ConditionFalse, @@ -617,11 +651,8 @@ func (t *Translator) processUnknownHTTPFilter( } } -func (t *Translator) processUnsupportedHTTPFilter( - filter v1beta1.HTTPRouteFilter, - filterContext *HTTPFiltersContext) { - // Unsupported filters. - errMsg := fmt.Sprintf("Unsupported filter type: %s", filter.Type) +func (t *Translator) processInvalidHTTPFilter(filter v1beta1.HTTPRouteFilter, filterContext *HTTPFiltersContext, err error) { + errMsg := fmt.Sprintf("Invalid filter %s: %v", filter.Type, err) filterContext.ParentRef.SetCondition(filterContext.HTTPRoute, v1beta1.RouteConditionAccepted, metav1.ConditionFalse, diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 6cc2d47a1a..981686a6d7 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -88,7 +88,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe // compute matches, filters, backends for ruleIdx, rule := range httpRoute.Spec.Rules { - httpFiltersContext := t.ProcessHTTPFilters(parentRef, httpRoute, rule.Filters) + httpFiltersContext := t.ProcessHTTPFilters(parentRef, httpRoute, rule.Filters, resources) // A rule is matched if any one of its matches // is satisfied (i.e. a logical "OR"), so generate diff --git a/internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.in.yaml b/internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.in.yaml new file mode 100644 index 0000000000..fa040d26f8 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.in.yaml @@ -0,0 +1,43 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + 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/v1beta1 + 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: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: AuthenticationFilter + name: test + diff --git a/internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.out.yaml b/internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.out.yaml new file mode 100644 index 0000000000..5be2dec721 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-non-existent-authenfilter-ref.out.yaml @@ -0,0 +1,102 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + 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 + status: + listeners: + - name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + attachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1beta1 + 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: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: AuthenticationFilter + name: test + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + sectionName: http + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: ResolvedRefs + status: "False" + reason: BackendNotFound + message: "Reference not found for filter type: ExtensionRef" +xdsIR: + envoy-gateway-gateway-1: + http: + - name: envoy-gateway-gateway-1-http + address: 0.0.0.0 + port: 10080 + hostnames: + - "*.envoyproxy.io" + routes: + - name: default-httproute-1-rule-0-match-0-gateway.envoyproxy.io + pathMatch: + prefix: "/" + headerMatches: + - name: ":authority" + exact: gateway.envoyproxy.io + # I believe the correct way to handle an invalid filter should be to allow the HTTPRoute to function + # normally but leave out the filter config and set the status, but this behaviour can be changed. + directResponse: + body: "Reference not found for filter type: ExtensionRef" + statusCode: 500 +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: http + protocol: "HTTP" + containerPort: 10080 + servicePort: 80 diff --git a/internal/gatewayapi/testdata/httproute-with-redirect-filter-invalid-filter-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-redirect-filter-invalid-filter-type.out.yaml index a9bf3c828f..dd99a16830 100644 --- a/internal/gatewayapi/testdata/httproute-with-redirect-filter-invalid-filter-type.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-redirect-filter-invalid-filter-type.out.yaml @@ -63,7 +63,7 @@ httpRoutes: - type: Accepted status: "False" reason: UnsupportedValue - message: "Unknown custom filter type: ExtensionRef" + message: "Invalid filter ExtensionRef: invalid group; must be gateway.envoyproxy.io" xdsIR: envoy-gateway-gateway-1: http: @@ -82,7 +82,7 @@ xdsIR: # I believe the correct way to handle an invalid filter should be to allow the HTTPRoute to function # normally but leave out the filter config and set the status, but this behaviour can be changed. directResponse: - body: "Unknown custom filter type: ExtensionRef" + body: "Invalid filter ExtensionRef: invalid group; must be gateway.envoyproxy.io" statusCode: 500 infraIR: envoy-gateway-gateway-1: diff --git a/internal/gatewayapi/testdata/httproute-with-valid-authenfilter.in.yaml b/internal/gatewayapi/testdata/httproute-with-valid-authenfilter.in.yaml new file mode 100644 index 0000000000..652978660a --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-valid-authenfilter.in.yaml @@ -0,0 +1,55 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + 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/v1beta1 + 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: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: AuthenticationFilter + name: test +authenFilters: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: AuthenticationFilter + metadata: + namespace: default + name: test + spec: + type: JWT + jwtProviders: + - name: test + issuer: https://www.test.local + remoteJWKS: + uri: https://test.local/jwt/public-key/jwks.json diff --git a/internal/gatewayapi/testdata/httproute-with-valid-authenfilter.out.yaml b/internal/gatewayapi/testdata/httproute-with-valid-authenfilter.out.yaml new file mode 100644 index 0000000000..d81b3f4c69 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-valid-authenfilter.out.yaml @@ -0,0 +1,101 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + 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 + status: + listeners: + - name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + attachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1beta1 + 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: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: AuthenticationFilter + name: test + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + sectionName: http + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +xdsIR: + envoy-gateway-gateway-1: + http: + - name: envoy-gateway-gateway-1-http + address: 0.0.0.0 + port: 10080 + hostnames: + - "*.envoyproxy.io" + routes: + - name: default-httproute-1-rule-0-match-0-gateway.envoyproxy.io + pathMatch: + prefix: "/" + headerMatches: + - name: ":authority" + exact: gateway.envoyproxy.io + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: http + protocol: "HTTP" + containerPort: 10080 + servicePort: 80