From 957889d66ced68cf3e071e93164a833f7e03122e Mon Sep 17 00:00:00 2001 From: KevFan Date: Thu, 21 Mar 2024 12:48:24 +0000 Subject: [PATCH 01/10] feat: ap default field --- api/v1beta2/authpolicy_types.go | 12 + .../manifests/kuadrant.io_authpolicies.yaml | 4302 +++++++++++++++++ .../crd/bases/kuadrant.io_authpolicies.yaml | 4302 +++++++++++++++++ 3 files changed, 8616 insertions(+) diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index aac60eb66..2fa45e7f7 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -141,6 +141,18 @@ type AuthPolicySpec struct { // +kubebuilder:validation:XValidation:rule="self.kind == 'HTTPRoute' || self.kind == 'Gateway'",message="Invalid targetRef.kind. The only supported values are 'HTTPRoute' and 'Gateway'" TargetRef gatewayapiv1alpha2.PolicyTargetReference `json:"targetRef"` + // Defaults define explicit default values for this policy and for policies inheriting this policy. + // Defaults are mutually exclusive with implicit defaults defined by CommonSpec. + // +optional + Defaults CommonSpec `json:"defaults"` + + // CommonSpec defines implicit default values for this policy and for policies inheriting this policy. + // CommonSpec is mutually exclusive with explicit defaults defined by Defaults. + CommonSpec `json:""` +} + +// CommonSpec contains common shared fields for defaults and overrides +type CommonSpec struct { // Top-level route selectors. // If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the external authorization service. // At least one selected HTTPRoute rule must match to trigger the AuthPolicy. diff --git a/bundle/manifests/kuadrant.io_authpolicies.yaml b/bundle/manifests/kuadrant.io_authpolicies.yaml index f67cd21cd..da502b84b 100644 --- a/bundle/manifests/kuadrant.io_authpolicies.yaml +++ b/bundle/manifests/kuadrant.io_authpolicies.yaml @@ -66,6 +66,4308 @@ spec: type: object spec: properties: + defaults: + description: |- + Defaults define explicit default values for this policy and for policies inheriting this policy. + Defaults are mutually exclusive with implicit defaults defined by CommonSpec. + properties: + patterns: + additionalProperties: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + description: Named sets of patterns that can be referred in `when` + conditions and in pattern-matching authorization policy rules. + type: object + routeSelectors: + description: |- + Top-level route selectors. + If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the external authorization service. + At least one selected HTTPRoute rule must match to trigger the AuthPolicy. + If no route selectors are specified, the AuthPolicy will be enforced at all requests to the protected routes. + items: + description: |- + RouteSelector defines semantics for matching an HTTP request based on conditions + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + properties: + hostnames: + description: |- + Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array + matches: + description: |- + Matches define conditions used for matching the rule against incoming HTTP requests. + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: "HTTPRouteMatch defines the predicate used + to match requests to a given\naction. Multiple match + types are ANDed together, i.e. the match will\nevaluate + to true only if all conditions are satisfied.\n\n\nFor + example, the match below will match a HTTP request only + if its path\nstarts with `/foo` AND it contains the + `version: v1` header:\n\n\n```\nmatch:\n\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + + Support: Core (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + + Support: Core (Exact, PathPrefix) + + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start + with '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + + Support: Extended (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query + param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 15 + type: array + rules: + description: |- + The auth rules of the policy. + See Authorino's AuthConfig CRD for more details. + properties: + authentication: + additionalProperties: + properties: + anonymous: + description: Anonymous access. + type: object + apiKey: + description: Authentication based on API keys stored + in Kubernetes secrets. + properties: + allNamespaces: + default: false + description: |- + Whether Authorino should look for API key secrets in all namespaces or only in the same namespace as the AuthConfig. + Enabling this option in namespaced Authorino instances has no effect. + type: boolean + selector: + description: Label selector used by Authorino to + match secrets from the cluster storing valid credentials + to authenticate to this service + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - selector + type: object + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external + data in the cache before pulled again from the + source. + type: integer + required: + - key + type: object + credentials: + description: |- + Defines where credentials are required to be passed in the request for authentication based on this config. + If omitted, it defaults to credentials passed in the HTTP Authorization header and the "Bearer" prefix prepended to the secret credential value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + defaults: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Set default property values (claims) for the resolved identity object, that are set before appending the object to + the authorization JSON. If the property is already present in the resolved identity object, the default value is ignored. + It requires the resolved identity object to always be a JSON object. + Do not use this option with identity objects of other JSON types (array, string, etc). + type: object + jwt: + description: Authentication based on JWT tokens. + properties: + issuerUrl: + description: |- + URL of the issuer of the JWT. + If `jwksUrl` is omitted, Authorino will append the path to the OpenID Connect Well-Known Discovery endpoint + (i.e. "/.well-known/openid-configuration") to this URL, to discover the OIDC configuration where to obtain + the "jkws_uri" claim from. + The value must coincide with the value of the "iss" (issuer) claim of the discovered OpenID Connect configuration. + type: string + ttl: + description: |- + Decides how long to wait before refreshing the JWKS (in seconds). + If omitted, Authorino will never refresh the JWKS. + type: integer + type: object + kubernetesTokenReview: + description: Authentication by Kubernetes token review. + properties: + audiences: + description: |- + The list of audiences (scopes) that must be claimed in a Kubernetes authentication token supplied in the request, and reviewed by Authorino. + If omitted, Authorino will review tokens expecting the host name of the requested protected service amongst the audiences. + items: + type: string + type: array + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + oauth2Introspection: + description: Authentication by OAuth2 token introspection. + properties: + credentialsRef: + description: Reference to a Kubernetes secret in + the same namespace, that stores client credentials + to the OAuth2 server. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + endpoint: + description: The full URL of the token introspection + endpoint. + type: string + tokenTypeHint: + description: |- + The token type hint for the token introspection. + If omitted, it defaults to "access_token". + type: string + required: + - credentialsRef + - endpoint + type: object + overrides: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Overrides the resolved identity object by setting the additional properties (claims) specified in this config, + before appending the object to the authorization JSON. + It requires the resolved identity object to always be a JSON object. + Do not use this option with identity objects of other JSON types (array, string, etc). + type: object + plain: + description: |- + Identity object extracted from the context. + Use this method when authentication is performed beforehand by a proxy and the resulting object passed to Authorino as JSON in the auth request. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + required: + - selector + type: object + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + routeSelectors: + description: |- + Top-level route selectors. + If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the auth rule. + At least one selected HTTPRoute rule must match to trigger the auth rule. + If no route selectors are specified, the auth rule will be evaluated at all requests to the protected routes. + items: + description: |- + RouteSelector defines semantics for matching an HTTP request based on conditions + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + properties: + hostnames: + description: |- + Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array + matches: + description: |- + Matches define conditions used for matching the rule against incoming HTTP requests. + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: "HTTPRouteMatch defines the predicate + used to match requests to a given\naction. + Multiple match types are ANDed together, i.e. + the match will\nevaluate to true only if all + conditions are satisfied.\n\n\nFor example, + the match below will match a HTTP request + only if its path\nstarts with `/foo` AND it + contains the `version: v1` header:\n\n\n```\nmatch:\n\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + + Support: Core (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + + Support: Core (Exact, PathPrefix) + + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path + to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path + and start with '/' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', + 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] + || self.type == 'RegularExpression' + - message: must only contain valid characters + (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + + Support: Extended (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 15 + type: array + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to + be evaluated as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to + be evaluated as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern + expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + x509: + description: |- + Authentication based on client X.509 certificates. + The certificates presented by the clients must be signed by a trusted CA whose certificates are stored in Kubernetes secrets. + properties: + allNamespaces: + default: false + description: |- + Whether Authorino should look for TLS secrets in all namespaces or only in the same namespace as the AuthConfig. + Enabling this option in namespaced Authorino instances has no effect. + type: boolean + selector: + description: |- + Label selector used by Authorino to match secrets from the cluster storing trusted CA certificates to validate + clients trying to authenticate to this service + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - selector + type: object + type: object + description: |- + Authentication configs. + At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 14 + type: object + authorization: + additionalProperties: + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external + data in the cache before pulled again from the + source. + type: integer + required: + - key + type: object + kubernetesSubjectAccessReview: + description: Authorization by Kubernetes SubjectAccessReview + properties: + groups: + description: Groups the user must be a member of + or, if `user` is omitted, the groups to check + for authorization in the Kubernetes RBAC. + items: + type: string + type: array + resourceAttributes: + description: |- + Use resourceAttributes to check permissions on Kubernetes resources. + If omitted, it performs a non-resource SubjectAccessReview, with verb and path inferred from the request. + properties: + group: + description: |- + API group of the resource. + Use '*' for all API groups. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + name: + description: |- + Resource name + Omit it to check for authorization on all resources of the specified kind. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + namespace: + description: Namespace where the user must have + permissions on the resource. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + resource: + description: |- + Resource kind + Use '*' for all resource kinds. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + subresource: + description: Subresource kind + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + verb: + description: |- + Verb to check for authorization on the resource. + Use '*' for all verbs. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + user: + description: |- + User to check for authorization in the Kubernetes RBAC. + Omit it to check for group authorization only. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + opa: + description: Open Policy Agent (OPA) Rego policy. + properties: + allValues: + default: false + description: |- + Returns the value of all Rego rules in the virtual document. Values can be read in subsequent evaluators/phases of the Auth Pipeline. + Otherwise, only the default `allow` rule will be exposed. + Returning all Rego rules can affect performance of OPA policies during reconciliation (policy precompile) and at runtime. + type: boolean + externalPolicy: + description: |- + Settings for fetching the OPA policy from an external registry. + Use it alternatively to 'rego'. + For the configurations of the HTTP request, the following options are not implemented: 'method', 'body', 'bodyParameters', + 'contentType', 'headers', 'oauth2'. Use it only with: 'url', 'sharedSecret', 'credentials'. + properties: + body: + description: |- + Raw body of the HTTP request. + Supersedes 'bodyParameters'; use either one or the other. + Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + bodyParameters: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Custom parameters to encode in the body of the HTTP request. + Superseded by 'body'; use either one or the other. + Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). + type: object + contentType: + default: application/x-www-form-urlencoded + description: |- + Content-Type of the request body. Shapes how 'bodyParameters' are encoded. + Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. + enum: + - application/x-www-form-urlencoded + - application/json + type: string + credentials: + description: |- + Defines where client credentials will be passed in the request to the service. + If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + headers: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Custom headers in the HTTP request. + type: object + method: + default: GET + description: |- + HTTP verb used in the request to the service. Accepted values: GET (default), POST. + When the request method is POST, the authorization JSON is passed in the body of the request. + enum: + - GET + - POST + - PUT + - PATCH + - DELETE + - HEAD + - OPTIONS + - CONNECT + - TRACE + type: string + oauth2: + description: Authentication with the HTTP service + by OAuth2 Client Credentials grant. + properties: + cache: + default: true + description: |- + Caches and reuses the token until expired. + Set it to false to force fetch the token at every authorization request regardless of expiration. + type: boolean + clientId: + description: OAuth2 Client ID. + type: string + clientSecretRef: + description: Reference to a Kuberentes Secret + key that stores that OAuth2 Client Secret. + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + description: The name of the secret + in the Authorino's namespace to select + from. + type: string + required: + - key + - name + type: object + extraParams: + additionalProperties: + type: string + description: Optional extra parameters for + the requests to the token URL. + type: object + scopes: + description: Optional scopes for the client + credentials grant, if supported by he + OAuth2 server. + items: + type: string + type: array + tokenUrl: + description: Token endpoint URL of the OAuth2 + resource server. + type: string + required: + - clientId + - clientSecretRef + - tokenUrl + type: object + sharedSecretRef: + description: |- + Reference to a Secret key whose value will be passed by Authorino in the request. + The HTTP service can use the shared secret to authenticate the origin of the request. + Ignored if used together with oauth2. + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: The name of the secret in the + Authorino's namespace to select from. + type: string + required: + - key + - name + type: object + ttl: + description: Duration (in seconds) of the external + data in the cache before pulled again from + the source. + type: integer + url: + description: |- + Endpoint URL of the HTTP service. + The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported + by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. + E.g. https://ext-auth-server.io/metadata?p={request.path} + type: string + required: + - url + type: object + rego: + description: |- + Authorization policy as a Rego language document. + The Rego document must include the "allow" condition, set by Authorino to "false" by default (i.e. requests are unauthorized unless changed). + The Rego document must NOT include the "package" declaration in line 1. + type: string + type: object + patternMatching: + description: Pattern-matching authorization rules. + properties: + patterns: + items: + properties: + all: + description: A list of pattern expressions + to be evaluated as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions + to be evaluated as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern + expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - patterns + type: object + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + routeSelectors: + description: |- + Top-level route selectors. + If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the auth rule. + At least one selected HTTPRoute rule must match to trigger the auth rule. + If no route selectors are specified, the auth rule will be evaluated at all requests to the protected routes. + items: + description: |- + RouteSelector defines semantics for matching an HTTP request based on conditions + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + properties: + hostnames: + description: |- + Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array + matches: + description: |- + Matches define conditions used for matching the rule against incoming HTTP requests. + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: "HTTPRouteMatch defines the predicate + used to match requests to a given\naction. + Multiple match types are ANDed together, i.e. + the match will\nevaluate to true only if all + conditions are satisfied.\n\n\nFor example, + the match below will match a HTTP request + only if its path\nstarts with `/foo` AND it + contains the `version: v1` header:\n\n\n```\nmatch:\n\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + + Support: Core (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + + Support: Core (Exact, PathPrefix) + + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path + to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path + and start with '/' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', + 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] + || self.type == 'RegularExpression' + - message: must only contain valid characters + (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + + Support: Extended (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 15 + type: array + spicedb: + description: Authorization decision delegated to external + Authzed/SpiceDB server. + properties: + endpoint: + description: Hostname and port number to the GRPC + interface of the SpiceDB server (e.g. spicedb:50051). + type: string + insecure: + description: Insecure HTTP connection (i.e. disables + TLS verification) + type: boolean + permission: + description: The name of the permission (or relation) + on which to execute the check. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + resource: + description: The resource on which to check the + permission or relation. + properties: + kind: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + name: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + sharedSecretRef: + description: Reference to a Secret key whose value + will be used by Authorino to authenticate with + the Authzed service. + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + subject: + description: The subject that will be checked for + the permission or relation. + properties: + kind: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + name: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + required: + - endpoint + type: object + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to + be evaluated as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to + be evaluated as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern + expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + type: object + description: |- + Authorization policies. + All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 14 + type: object + callbacks: + additionalProperties: + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external + data in the cache before pulled again from the + source. + type: integer + required: + - key + type: object + http: + description: Settings of the external HTTP request + properties: + body: + description: |- + Raw body of the HTTP request. + Supersedes 'bodyParameters'; use either one or the other. + Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + bodyParameters: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Custom parameters to encode in the body of the HTTP request. + Superseded by 'body'; use either one or the other. + Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). + type: object + contentType: + default: application/x-www-form-urlencoded + description: |- + Content-Type of the request body. Shapes how 'bodyParameters' are encoded. + Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. + enum: + - application/x-www-form-urlencoded + - application/json + type: string + credentials: + description: |- + Defines where client credentials will be passed in the request to the service. + If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + headers: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Custom headers in the HTTP request. + type: object + method: + default: GET + description: |- + HTTP verb used in the request to the service. Accepted values: GET (default), POST. + When the request method is POST, the authorization JSON is passed in the body of the request. + enum: + - GET + - POST + - PUT + - PATCH + - DELETE + - HEAD + - OPTIONS + - CONNECT + - TRACE + type: string + oauth2: + description: Authentication with the HTTP service + by OAuth2 Client Credentials grant. + properties: + cache: + default: true + description: |- + Caches and reuses the token until expired. + Set it to false to force fetch the token at every authorization request regardless of expiration. + type: boolean + clientId: + description: OAuth2 Client ID. + type: string + clientSecretRef: + description: Reference to a Kuberentes Secret + key that stores that OAuth2 Client Secret. + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: The name of the secret in the + Authorino's namespace to select from. + type: string + required: + - key + - name + type: object + extraParams: + additionalProperties: + type: string + description: Optional extra parameters for the + requests to the token URL. + type: object + scopes: + description: Optional scopes for the client + credentials grant, if supported by he OAuth2 + server. + items: + type: string + type: array + tokenUrl: + description: Token endpoint URL of the OAuth2 + resource server. + type: string + required: + - clientId + - clientSecretRef + - tokenUrl + type: object + sharedSecretRef: + description: |- + Reference to a Secret key whose value will be passed by Authorino in the request. + The HTTP service can use the shared secret to authenticate the origin of the request. + Ignored if used together with oauth2. + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + url: + description: |- + Endpoint URL of the HTTP service. + The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported + by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. + E.g. https://ext-auth-server.io/metadata?p={request.path} + type: string + required: + - url + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + routeSelectors: + description: |- + Top-level route selectors. + If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the auth rule. + At least one selected HTTPRoute rule must match to trigger the auth rule. + If no route selectors are specified, the auth rule will be evaluated at all requests to the protected routes. + items: + description: |- + RouteSelector defines semantics for matching an HTTP request based on conditions + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + properties: + hostnames: + description: |- + Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array + matches: + description: |- + Matches define conditions used for matching the rule against incoming HTTP requests. + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: "HTTPRouteMatch defines the predicate + used to match requests to a given\naction. + Multiple match types are ANDed together, i.e. + the match will\nevaluate to true only if all + conditions are satisfied.\n\n\nFor example, + the match below will match a HTTP request + only if its path\nstarts with `/foo` AND it + contains the `version: v1` header:\n\n\n```\nmatch:\n\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + + Support: Core (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + + Support: Core (Exact, PathPrefix) + + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path + to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path + and start with '/' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', + 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] + || self.type == 'RegularExpression' + - message: must only contain valid characters + (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + + Support: Extended (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 15 + type: array + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to + be evaluated as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to + be evaluated as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern + expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - http + type: object + description: |- + Callback functions. + Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 14 + type: object + metadata: + additionalProperties: + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external + data in the cache before pulled again from the + source. + type: integer + required: + - key + type: object + http: + description: External source of auth metadata via HTTP + request + properties: + body: + description: |- + Raw body of the HTTP request. + Supersedes 'bodyParameters'; use either one or the other. + Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + bodyParameters: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Custom parameters to encode in the body of the HTTP request. + Superseded by 'body'; use either one or the other. + Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). + type: object + contentType: + default: application/x-www-form-urlencoded + description: |- + Content-Type of the request body. Shapes how 'bodyParameters' are encoded. + Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. + enum: + - application/x-www-form-urlencoded + - application/json + type: string + credentials: + description: |- + Defines where client credentials will be passed in the request to the service. + If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + headers: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Custom headers in the HTTP request. + type: object + method: + default: GET + description: |- + HTTP verb used in the request to the service. Accepted values: GET (default), POST. + When the request method is POST, the authorization JSON is passed in the body of the request. + enum: + - GET + - POST + - PUT + - PATCH + - DELETE + - HEAD + - OPTIONS + - CONNECT + - TRACE + type: string + oauth2: + description: Authentication with the HTTP service + by OAuth2 Client Credentials grant. + properties: + cache: + default: true + description: |- + Caches and reuses the token until expired. + Set it to false to force fetch the token at every authorization request regardless of expiration. + type: boolean + clientId: + description: OAuth2 Client ID. + type: string + clientSecretRef: + description: Reference to a Kuberentes Secret + key that stores that OAuth2 Client Secret. + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: The name of the secret in the + Authorino's namespace to select from. + type: string + required: + - key + - name + type: object + extraParams: + additionalProperties: + type: string + description: Optional extra parameters for the + requests to the token URL. + type: object + scopes: + description: Optional scopes for the client + credentials grant, if supported by he OAuth2 + server. + items: + type: string + type: array + tokenUrl: + description: Token endpoint URL of the OAuth2 + resource server. + type: string + required: + - clientId + - clientSecretRef + - tokenUrl + type: object + sharedSecretRef: + description: |- + Reference to a Secret key whose value will be passed by Authorino in the request. + The HTTP service can use the shared secret to authenticate the origin of the request. + Ignored if used together with oauth2. + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + url: + description: |- + Endpoint URL of the HTTP service. + The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported + by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. + E.g. https://ext-auth-server.io/metadata?p={request.path} + type: string + required: + - url + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + routeSelectors: + description: |- + Top-level route selectors. + If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the auth rule. + At least one selected HTTPRoute rule must match to trigger the auth rule. + If no route selectors are specified, the auth rule will be evaluated at all requests to the protected routes. + items: + description: |- + RouteSelector defines semantics for matching an HTTP request based on conditions + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + properties: + hostnames: + description: |- + Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array + matches: + description: |- + Matches define conditions used for matching the rule against incoming HTTP requests. + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: "HTTPRouteMatch defines the predicate + used to match requests to a given\naction. + Multiple match types are ANDed together, i.e. + the match will\nevaluate to true only if all + conditions are satisfied.\n\n\nFor example, + the match below will match a HTTP request + only if its path\nstarts with `/foo` AND it + contains the `version: v1` header:\n\n\n```\nmatch:\n\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + + Support: Core (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + + Support: Core (Exact, PathPrefix) + + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path + to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path + and start with '/' when type one of + ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type + one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', + 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] + || self.type == 'RegularExpression' + - message: must only contain valid characters + (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + + Support: Extended (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of + HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 15 + type: array + uma: + description: User-Managed Access (UMA) source of resource + data. + properties: + credentialsRef: + description: Reference to a Kubernetes secret in + the same namespace, that stores client credentials + to the resource registration API of the UMA server. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + endpoint: + description: |- + The endpoint of the UMA server. + The value must coincide with the "issuer" claim of the UMA config discovered from the well-known uma configuration endpoint. + type: string + required: + - credentialsRef + - endpoint + type: object + userInfo: + description: OpendID Connect UserInfo linked to an OIDC + authentication config specified in this same AuthConfig. + properties: + identitySource: + description: The name of an OIDC-enabled JWT authentication + config whose OpenID Connect configuration discovered + includes the OIDC "userinfo_endpoint" claim. + type: string + required: + - identitySource + type: object + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to + be evaluated as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to + be evaluated as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern + expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + type: object + description: |- + Metadata sources. + Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 14 + type: object + response: + description: |- + Response items. + Authorino builds custom responses to the client of the auth request. + properties: + success: + description: |- + Response items to be included in the auth response when the request is authenticated and authorized. + For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. + properties: + dynamicMetadata: + additionalProperties: + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the + external data in the cache before pulled + again from the source. + type: integer + required: + - key + type: object + json: + description: |- + JSON object + Specify it as the list of properties of the object, whose values can combine static values and values selected from the authorization JSON. + properties: + properties: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + required: + - properties + type: object + key: + description: |- + The key used to add the custom response item (name of the HTTP header or root property of the Dynamic Metadata object). + If omitted, it will be set to the name of the response config. + type: string + metrics: + default: false + description: Whether this config should generate + individual observability metrics + type: boolean + plain: + description: Plain text content + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + routeSelectors: + description: |- + Top-level route selectors. + If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the auth rule. + At least one selected HTTPRoute rule must match to trigger the auth rule. + If no route selectors are specified, the auth rule will be evaluated at all requests to the protected routes. + items: + description: |- + RouteSelector defines semantics for matching an HTTP request based on conditions + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + properties: + hostnames: + description: |- + Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array + matches: + description: |- + Matches define conditions used for matching the rule against incoming HTTP requests. + https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec + items: + description: "HTTPRouteMatch defines + the predicate used to match requests + to a given\naction. Multiple match + types are ANDed together, i.e. the + match will\nevaluate to true only + if all conditions are satisfied.\n\n\nFor + example, the match below will match + a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: + v1` header:\n\n\n```\nmatch:\n\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- + name: \"version\"\n\t value \"v1\"\n\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + + Support: Core (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the + value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + + Support: Core (Exact, PathPrefix) + + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP + path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute + path and start with '/' when + type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' + when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', + 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] + || self.type == 'RegularExpression' + - message: must only contain valid + characters (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) + ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + + Support: Extended (Exact) + + + Support: Implementation-specific (RegularExpression) + + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the + value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 15 + type: array + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions + to be evaluated as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions + to be evaluated as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set + of pattern expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + wristband: + description: Authorino Festival Wristband token + properties: + customClaims: + additionalProperties: + properties: + selector: + description: |- + Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Any claims to be added to the + wristband token apart from the standard + JWT claims (iss, iat, exp) added by default. + type: object + issuer: + description: 'The endpoint to the Authorino + service that issues the wristband (format: + ://:/, where + = /://:/, where + = /://:/, where + = /://:/, where + = / Date: Thu, 21 Mar 2024 13:19:16 +0000 Subject: [PATCH 02/10] refactor: Getters for common shared default fields --- api/v1beta2/authpolicy_types.go | 61 ++++++++++---- api/v1beta2/authpolicy_types_test.go | 32 ++++---- api/v1beta2/zz_generated.deepcopy.go | 81 ++++++++++++------- controllers/authpolicy_authconfig.go | 24 +++--- .../authpolicy_istio_authorizationpolicy.go | 2 +- 5 files changed, 125 insertions(+), 75 deletions(-) diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index 2fa45e7f7..a77f16536 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -144,7 +144,7 @@ type AuthPolicySpec struct { // Defaults define explicit default values for this policy and for policies inheriting this policy. // Defaults are mutually exclusive with implicit defaults defined by CommonSpec. // +optional - Defaults CommonSpec `json:"defaults"` + Defaults *CommonSpec `json:"defaults"` // CommonSpec defines implicit default values for this policy and for policies inheriting this policy. // CommonSpec is mutually exclusive with explicit defaults defined by Defaults. @@ -179,6 +179,9 @@ type CommonSpec struct { // GetRouteSelectors returns the top-level route selectors of the auth scheme. // impl: RouteSelectorsGetter func (s AuthPolicySpec) GetRouteSelectors() []RouteSelector { + if s.Defaults != nil { + return s.Defaults.RouteSelectors + } return s.RouteSelectors } @@ -274,28 +277,32 @@ func (ap *AuthPolicy) GetRulesHostnames() (ruleHosts []string) { } } - appendRuleHosts(ap.Spec) - for _, config := range ap.Spec.AuthScheme.Authentication { - appendRuleHosts(config) - } - for _, config := range ap.Spec.AuthScheme.Metadata { - appendRuleHosts(config) - } - for _, config := range ap.Spec.AuthScheme.Authorization { - appendRuleHosts(config) - } - if response := ap.Spec.AuthScheme.Response; response != nil { - for _, config := range response.Success.Headers { + appendCommonSpecRuleHosts := func(c CommonSpec) { + for _, config := range c.AuthScheme.Authentication { appendRuleHosts(config) } - for _, config := range response.Success.DynamicMetadata { + for _, config := range c.AuthScheme.Metadata { + appendRuleHosts(config) + } + for _, config := range c.AuthScheme.Authorization { + appendRuleHosts(config) + } + if response := c.AuthScheme.Response; response != nil { + for _, config := range response.Success.Headers { + appendRuleHosts(config) + } + for _, config := range response.Success.DynamicMetadata { + appendRuleHosts(config) + } + } + for _, config := range c.AuthScheme.Callbacks { appendRuleHosts(config) } - } - for _, config := range ap.Spec.AuthScheme.Callbacks { - appendRuleHosts(config) } + appendRuleHosts(ap.Spec) + appendCommonSpecRuleHosts(ap.GetCommonSpec()) + return } @@ -311,6 +318,26 @@ func (ap *AuthPolicy) DirectReferenceAnnotationName() string { return AuthPolicyDirectReferenceAnnotationName } +func (ap *AuthPolicy) GetCommonSpec() CommonSpec { + if ap.Spec.Defaults != nil { + return *ap.Spec.Defaults + } + + return ap.Spec.CommonSpec +} + +func (ap *AuthPolicy) GetNamedPatterns() map[string]authorinoapi.PatternExpressions { + return ap.GetCommonSpec().NamedPatterns +} + +func (ap *AuthPolicy) GetConditions() []authorinoapi.PatternExpressionOrRef { + return ap.GetCommonSpec().Conditions +} + +func (ap *AuthPolicy) GetAuthScheme() AuthSchemeSpec { + return ap.GetCommonSpec().AuthScheme +} + //+kubebuilder:object:root=true // AuthPolicyList contains a list of AuthPolicy diff --git a/api/v1beta2/authpolicy_types_test.go b/api/v1beta2/authpolicy_types_test.go index 4e9fbd3be..9bd32f349 100644 --- a/api/v1beta2/authpolicy_types_test.go +++ b/api/v1beta2/authpolicy_types_test.go @@ -261,22 +261,24 @@ func TestAuthPolicyValidate(t *testing.T) { Name: "my-route", Namespace: ptr.To(gatewayapiv1.Namespace("other-namespace")), }, - AuthScheme: AuthSchemeSpec{ - Authentication: map[string]AuthenticationSpec{ - "my-rule": { - AuthenticationSpec: authorinoapi.AuthenticationSpec{ - AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ - AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + CommonSpec: CommonSpec{ + AuthScheme: AuthSchemeSpec{ + Authentication: map[string]AuthenticationSpec{ + "my-rule": { + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + }, }, - }, - CommonAuthRuleSpec: CommonAuthRuleSpec{ - RouteSelectors: []RouteSelector{ - { - Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Value: ptr.To("/foo"), + CommonAuthRuleSpec: CommonAuthRuleSpec{ + RouteSelectors: []RouteSelector{ + { + Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, + Matches: []gatewayapiv1.HTTPRouteMatch{ + { + Path: &gatewayapiv1.HTTPPathMatch{ + Value: ptr.To("/foo"), + }, }, }, }, diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index e7df27649..ae44ce26b 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -90,37 +90,12 @@ func (in *AuthPolicyList) DeepCopyObject() runtime.Object { func (in *AuthPolicySpec) DeepCopyInto(out *AuthPolicySpec) { *out = *in in.TargetRef.DeepCopyInto(&out.TargetRef) - if in.RouteSelectors != nil { - in, out := &in.RouteSelectors, &out.RouteSelectors - *out = make([]RouteSelector, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.NamedPatterns != nil { - in, out := &in.NamedPatterns, &out.NamedPatterns - *out = make(map[string]apiv1beta2.PatternExpressions, len(*in)) - for key, val := range *in { - var outVal []apiv1beta2.PatternExpression - if val == nil { - (*out)[key] = nil - } else { - inVal := (*in)[key] - in, out := &inVal, &outVal - *out = make(apiv1beta2.PatternExpressions, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]apiv1beta2.PatternExpressionOrRef, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + if in.Defaults != nil { + in, out := &in.Defaults, &out.Defaults + *out = new(CommonSpec) + (*in).DeepCopyInto(*out) } - in.AuthScheme.DeepCopyInto(&out.AuthScheme) + in.CommonSpec.DeepCopyInto(&out.CommonSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthPolicySpec. @@ -276,6 +251,52 @@ func (in *CommonAuthRuleSpec) DeepCopy() *CommonAuthRuleSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CommonSpec) DeepCopyInto(out *CommonSpec) { + *out = *in + if in.RouteSelectors != nil { + in, out := &in.RouteSelectors, &out.RouteSelectors + *out = make([]RouteSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NamedPatterns != nil { + in, out := &in.NamedPatterns, &out.NamedPatterns + *out = make(map[string]apiv1beta2.PatternExpressions, len(*in)) + for key, val := range *in { + var outVal []apiv1beta2.PatternExpression + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make(apiv1beta2.PatternExpressions, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]apiv1beta2.PatternExpressionOrRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.AuthScheme.DeepCopyInto(&out.AuthScheme) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonSpec. +func (in *CommonSpec) DeepCopy() *CommonSpec { + if in == nil { + return nil + } + out := new(CommonSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HeaderSuccessResponseSpec) DeepCopyInto(out *HeaderSuccessResponseSpec) { *out = *in diff --git a/controllers/authpolicy_authconfig.go b/controllers/authpolicy_authconfig.go index a3da417b9..4a1d6464b 100644 --- a/controllers/authpolicy_authconfig.go +++ b/controllers/authpolicy_authconfig.go @@ -112,7 +112,7 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au authConfig.Spec.Hosts = hosts // named patterns - if namedPatterns := ap.Spec.NamedPatterns; len(namedPatterns) > 0 { + if namedPatterns := ap.GetNamedPatterns(); len(namedPatterns) > 0 { authConfig.Spec.NamedPatterns = namedPatterns } @@ -124,27 +124,27 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au if len(topLevelConditionsFromRouteSelectors) == 0 { topLevelConditionsFromRouteSelectors = authorinoConditionsFromHTTPRoute(route) } - if len(topLevelConditionsFromRouteSelectors) > 0 || len(ap.Spec.Conditions) > 0 { + if len(topLevelConditionsFromRouteSelectors) > 0 || len(ap.GetConditions()) > 0 { authConfig.Spec.Conditions = append(ap.Spec.Conditions, topLevelConditionsFromRouteSelectors...) } // authentication - if authentication := ap.Spec.AuthScheme.Authentication; len(authentication) > 0 { + if authentication := ap.GetAuthScheme().Authentication; len(authentication) > 0 { authConfig.Spec.Authentication = authorinoSpecsFromConfigs(authentication, func(config api.AuthenticationSpec) authorinoapi.AuthenticationSpec { return config.AuthenticationSpec }) } // metadata - if metadata := ap.Spec.AuthScheme.Metadata; len(metadata) > 0 { + if metadata := ap.GetAuthScheme().Metadata; len(metadata) > 0 { authConfig.Spec.Metadata = authorinoSpecsFromConfigs(metadata, func(config api.MetadataSpec) authorinoapi.MetadataSpec { return config.MetadataSpec }) } // authorization - if authorization := ap.Spec.AuthScheme.Authorization; len(authorization) > 0 { + if authorization := ap.GetAuthScheme().Authorization; len(authorization) > 0 { authConfig.Spec.Authorization = authorinoSpecsFromConfigs(authorization, func(config api.AuthorizationSpec) authorinoapi.AuthorizationSpec { return config.AuthorizationSpec }) } // response - if response := ap.Spec.AuthScheme.Response; response != nil { + if response := ap.GetAuthScheme().Response; response != nil { authConfig.Spec.Response = &authorinoapi.ResponseSpec{ Unauthenticated: response.Unauthenticated, Unauthorized: response.Unauthorized, @@ -160,7 +160,7 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au } // callbacks - if callbacks := ap.Spec.AuthScheme.Callbacks; len(callbacks) > 0 { + if callbacks := ap.GetAuthScheme().Callbacks; len(callbacks) > 0 { authConfig.Spec.Callbacks = authorinoSpecsFromConfigs(callbacks, func(config api.CallbackSpec) authorinoapi.CallbackSpec { return config.CallbackSpec }) } @@ -188,7 +188,7 @@ func authorinoSpecsFromConfigs[T, U any](configs map[string]U, extractAuthorinoS func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gatewayapiv1.HTTPRoute, authConfig *authorinoapi.AuthConfig) (*authorinoapi.AuthConfig, error) { // authentication - for name, config := range ap.Spec.AuthScheme.Authentication { + for name, config := range ap.GetAuthScheme().Authentication { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err @@ -202,7 +202,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // metadata - for name, config := range ap.Spec.AuthScheme.Metadata { + for name, config := range ap.GetAuthScheme().Metadata { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err @@ -216,7 +216,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // authorization - for name, config := range ap.Spec.AuthScheme.Authorization { + for name, config := range ap.GetAuthScheme().Authorization { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err @@ -230,7 +230,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // response - if response := ap.Spec.AuthScheme.Response; response != nil { + if response := ap.GetAuthScheme().Response; response != nil { // response success headers for name, config := range response.Success.Headers { conditions, err := authorinoConditionsFromRouteSelectors(route, config) @@ -261,7 +261,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // callbacks - for name, config := range ap.Spec.AuthScheme.Callbacks { + for name, config := range ap.GetAuthScheme().Callbacks { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err diff --git a/controllers/authpolicy_istio_authorizationpolicy.go b/controllers/authpolicy_istio_authorizationpolicy.go index b44c56a64..54e6c75b5 100644 --- a/controllers/authpolicy_istio_authorizationpolicy.go +++ b/controllers/authpolicy_istio_authorizationpolicy.go @@ -193,7 +193,7 @@ func istioAuthorizationPolicyLabels(gwKey, apKey client.ObjectKey) map[string]st // If the route selectors specified in the policy do not match any route rules, an error is returned. func istioAuthorizationPolicyRules(ap *api.AuthPolicy, route *gatewayapiv1.HTTPRoute) ([]*istiosecurity.Rule, error) { // use only the top level route selectors if defined - if topLevelRouteSelectors := ap.Spec.RouteSelectors; len(topLevelRouteSelectors) > 0 { + if topLevelRouteSelectors := ap.Spec.GetRouteSelectors(); len(topLevelRouteSelectors) > 0 { return istioAuthorizationPolicyRulesFromRouteSelectors(route, topLevelRouteSelectors) } return istioAuthorizationPolicyRulesFromHTTPRoute(route), nil From 38629a24d0f999afeeccc92b45bc263b59404d8f Mon Sep 17 00:00:00 2001 From: KevFan Date: Thu, 21 Mar 2024 15:42:34 +0000 Subject: [PATCH 03/10] workaround: reduce route selector length & use server side apply --- Makefile | 5 ++-- api/v1beta2/authpolicy_types.go | 4 +-- .../manifests/kuadrant.io_authpolicies.yaml | 28 +++++++++---------- .../crd/bases/kuadrant.io_authpolicies.yaml | 28 +++++++++---------- controllers/authpolicy_controller_test.go | 4 ++- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 4b5fe625d..9ed062384 100644 --- a/Makefile +++ b/Makefile @@ -406,14 +406,15 @@ kind-load-bundle: ## Load image to local cluster ##@ Deployment install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | kubectl apply -f - + # Use server side apply, otherwise will hit into this issue https://medium.com/pareture/kubectl-install-crd-failed-annotations-too-long-2ebc91b40c7d + $(KUSTOMIZE) build config/crd | kubectl apply --server-side -f - uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests dependencies-manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/deploy | kubectl apply -f - + $(KUSTOMIZE) build config/deploy | kubectl apply --server-side -f - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMAGE_TAG_BASE):latest undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index a77f16536..79801853d 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -57,7 +57,7 @@ type CommonAuthRuleSpec struct { // At least one selected HTTPRoute rule must match to trigger the auth rule. // If no route selectors are specified, the auth rule will be evaluated at all requests to the protected routes. // +optional - // +kubebuilder:validation:MaxItems=15 + // +kubebuilder:validation:MaxItems=8 RouteSelectors []RouteSelector `json:"routeSelectors,omitempty"` } @@ -158,7 +158,7 @@ type CommonSpec struct { // At least one selected HTTPRoute rule must match to trigger the AuthPolicy. // If no route selectors are specified, the AuthPolicy will be enforced at all requests to the protected routes. // +optional - // +kubebuilder:validation:MaxItems=15 + // +kubebuilder:validation:MaxItems=8 RouteSelectors []RouteSelector `json:"routeSelectors,omitempty"` // Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. diff --git a/bundle/manifests/kuadrant.io_authpolicies.yaml b/bundle/manifests/kuadrant.io_authpolicies.yaml index da502b84b..172cace67 100644 --- a/bundle/manifests/kuadrant.io_authpolicies.yaml +++ b/bundle/manifests/kuadrant.io_authpolicies.yaml @@ -396,7 +396,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array rules: description: |- @@ -947,7 +947,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -1791,7 +1791,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array spicedb: description: Authorization decision delegated to external @@ -2480,7 +2480,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -3068,7 +3068,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array uma: description: User-Managed Access (UMA) source of resource @@ -3566,7 +3566,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -4080,7 +4080,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -4691,7 +4691,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array rules: description: |- @@ -5240,7 +5240,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -6079,7 +6079,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array spicedb: description: Authorization decision delegated to external @@ -6765,7 +6765,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -7349,7 +7349,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array uma: description: User-Managed Access (UMA) source of resource @@ -7843,7 +7843,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -8353,7 +8353,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- diff --git a/config/crd/bases/kuadrant.io_authpolicies.yaml b/config/crd/bases/kuadrant.io_authpolicies.yaml index 039af1b1d..995961fe5 100644 --- a/config/crd/bases/kuadrant.io_authpolicies.yaml +++ b/config/crd/bases/kuadrant.io_authpolicies.yaml @@ -395,7 +395,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array rules: description: |- @@ -946,7 +946,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -1790,7 +1790,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array spicedb: description: Authorization decision delegated to external @@ -2479,7 +2479,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -3067,7 +3067,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array uma: description: User-Managed Access (UMA) source of resource @@ -3565,7 +3565,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -4079,7 +4079,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -4690,7 +4690,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array rules: description: |- @@ -5239,7 +5239,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -6078,7 +6078,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array spicedb: description: Authorization decision delegated to external @@ -6764,7 +6764,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -7348,7 +7348,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array uma: description: User-Managed Access (UMA) source of resource @@ -7842,7 +7842,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- @@ -8352,7 +8352,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 15 + maxItems: 8 type: array when: description: |- diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index 3ac0bf529..581b03433 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -66,7 +66,9 @@ var _ = Describe("AuthPolicy controller", func() { Name: testHTTPRouteName, Namespace: ptr.To(gatewayapiv1.Namespace(testNamespace)), }, - AuthScheme: testBasicAuthScheme(), + CommonSpec: api.CommonSpec{ + AuthScheme: testBasicAuthScheme(), + }, }, } for _, mutateFn := range mutateFns { From 9a6b5350839dd01eb13e8013e7eb84c69bae0172 Mon Sep 17 00:00:00 2001 From: KevFan Date: Fri, 22 Mar 2024 11:44:14 +0000 Subject: [PATCH 04/10] refactor: use explicit default in AP integration tests --- controllers/authpolicy_authconfig.go | 2 +- controllers/authpolicy_controller_test.go | 40 +++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/controllers/authpolicy_authconfig.go b/controllers/authpolicy_authconfig.go index 4a1d6464b..53f06cfa6 100644 --- a/controllers/authpolicy_authconfig.go +++ b/controllers/authpolicy_authconfig.go @@ -125,7 +125,7 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au topLevelConditionsFromRouteSelectors = authorinoConditionsFromHTTPRoute(route) } if len(topLevelConditionsFromRouteSelectors) > 0 || len(ap.GetConditions()) > 0 { - authConfig.Spec.Conditions = append(ap.Spec.Conditions, topLevelConditionsFromRouteSelectors...) + authConfig.Spec.Conditions = append(ap.GetConditions(), topLevelConditionsFromRouteSelectors...) } // authentication diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index 581b03433..569381c32 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -66,7 +66,7 @@ var _ = Describe("AuthPolicy controller", func() { Name: testHTTPRouteName, Namespace: ptr.To(gatewayapiv1.Namespace(testNamespace)), }, - CommonSpec: api.CommonSpec{ + Defaults: &api.CommonSpec{ AuthScheme: testBasicAuthScheme(), }, }, @@ -94,7 +94,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = testGatewayName - policy.Spec.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" + policy.Spec.Defaults.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) err := k8sClient.Create(context.Background(), policy) @@ -314,7 +314,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Rejects policy with only unmatching top-level route selectors while trying to configure the gateway", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.RouteSelectors = []api.RouteSelector{ + policy.Spec.Defaults.RouteSelectors = []api.RouteSelector{ { // does not select any HTTPRouteRule Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ { @@ -358,7 +358,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Rejects policy with only unmatching config-level route selectors post-configuring the gateway", func() { policy := policyFactory() - config := policy.Spec.AuthScheme.Authentication["apiKey"] + config := policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] config.RouteSelectors = []api.RouteSelector{ { // does not select any HTTPRouteRule Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ @@ -368,7 +368,7 @@ var _ = Describe("AuthPolicy controller", func() { }, }, } - policy.Spec.AuthScheme.Authentication["apiKey"] = config + policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] = config err := k8sClient.Create(context.Background(), policy) logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(policy).String(), "error", err) @@ -440,7 +440,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Maps to all fields of the AuthConfig", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.NamedPatterns = map[string]authorinoapi.PatternExpressions{ + policy.Spec.Defaults.NamedPatterns = map[string]authorinoapi.PatternExpressions{ "internal-source": []authorinoapi.PatternExpression{ { Selector: "source.ip", @@ -456,14 +456,14 @@ var _ = Describe("AuthPolicy controller", func() { }, }, } - policy.Spec.Conditions = []authorinoapi.PatternExpressionOrRef{ + policy.Spec.Defaults.Conditions = []authorinoapi.PatternExpressionOrRef{ { PatternRef: authorinoapi.PatternRef{ Name: "internal-source", }, }, } - policy.Spec.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "jwt": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ @@ -757,7 +757,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Attaches policy with top-level route selectors to the HTTPRoute", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.RouteSelectors = []api.RouteSelector{ + policy.Spec.Defaults.RouteSelectors = []api.RouteSelector{ { // Selects: POST|DELETE *.admin.toystore.com/admin* Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ { @@ -862,7 +862,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Attaches policy with config-level route selectors to the HTTPRoute", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - config := policy.Spec.AuthScheme.Authentication["apiKey"] + config := policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] config.RouteSelectors = []api.RouteSelector{ { // Selects: POST|DELETE *.admin.toystore.com/admin* Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ @@ -876,7 +876,7 @@ var _ = Describe("AuthPolicy controller", func() { Hostnames: []gatewayapiv1.Hostname{"*.admin.toystore.com"}, }, } - policy.Spec.AuthScheme.Authentication["apiKey"] = config + policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] = config }) err := k8sClient.Create(context.Background(), policy) @@ -977,7 +977,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Mixes route selectors into other conditions", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - config := policy.Spec.AuthScheme.Authentication["apiKey"] + config := policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] config.RouteSelectors = []api.RouteSelector{ { // Selects: GET /private* Matches: []gatewayapiv1.HTTPRouteMatch{ @@ -1000,7 +1000,7 @@ var _ = Describe("AuthPolicy controller", func() { }, }, } - policy.Spec.AuthScheme.Authentication["apiKey"] = config + policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] = config }) err := k8sClient.Create(context.Background(), policy) @@ -1371,7 +1371,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { } It("invalid usage of top-level route selectors with a gateway targetRef", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.RouteSelectors = routeSelectors + policy.Spec.Defaults.RouteSelectors = routeSelectors }) err := k8sClient.Create(context.Background(), policy) @@ -1381,7 +1381,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authentication", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "my-rule": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ @@ -1402,7 +1402,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - metadata", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ Metadata: map[string]api.MetadataSpec{ "my-metadata": { CommonAuthRuleSpec: commonAuthRuleSpec, @@ -1418,7 +1418,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authorization", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ Authorization: map[string]api.AuthorizationSpec{ "my-authZ": { CommonAuthRuleSpec: commonAuthRuleSpec, @@ -1434,7 +1434,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success headers", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ Headers: map[string]api.HeaderSuccessResponseSpec{ @@ -1456,7 +1456,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ DynamicMetadata: map[string]api.SuccessResponseSpec{ @@ -1476,7 +1476,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - callbacks", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ Callbacks: map[string]api.CallbackSpec{ "callback": { CallbackSpec: authorinoapi.CallbackSpec{ From 7d9845499dd452d50dce5e0bbf1fad999278384b Mon Sep 17 00:00:00 2001 From: KevFan Date: Fri, 22 Mar 2024 15:59:21 +0000 Subject: [PATCH 05/10] refactor: AP CEL validation --- api/v1beta2/authpolicy_types.go | 50 ++-- api/v1beta2/authpolicy_types_test.go | 11 +- api/v1beta2/zz_generated.deepcopy.go | 6 +- .../manifests/kuadrant.io_authpolicies.yaml | 47 ++- .../crd/bases/kuadrant.io_authpolicies.yaml | 47 ++- controllers/authpolicy_authconfig.go | 2 +- controllers/authpolicy_controller_test.go | 275 ++++++++++++++++-- .../authpolicy_istio_authorizationpolicy.go | 2 +- 8 files changed, 373 insertions(+), 67 deletions(-) diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index 79801853d..dcf073039 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -128,13 +128,24 @@ type CallbackSpec struct { CommonAuthRuleSpec `json:""` } +// RouteSelectors - implicit default validation // +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.routeSelectors)",message="route selectors not supported when targeting a Gateway" -// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.authentication) || !self.rules.authentication.exists(x, has(self.rules.authentication[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" -// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.metadata) || !self.rules.metadata.exists(x, has(self.rules.metadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" -// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.authorization) || !self.rules.authorization.exists(x, has(self.rules.authorization[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" -// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.headers) || !self.rules.response.success.headers.exists(x, has(self.rules.response.success.headers[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" -// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.dynamicMetadata) || !self.rules.response.success.dynamicMetadata.exists(x, has(self.rules.response.success.dynamicMetadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" -// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.callbacks) || !self.rules.callbacks.exists(x, has(self.rules.callbacks[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.authentication) || !self.rules.authentication.exists(x, has(self.rules.authentication[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.metadata) || !self.rules.metadata.exists(x, has(self.rules.metadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.authorization) || !self.rules.authorization.exists(x, has(self.rules.authorization[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.headers) || !self.rules.response.success.headers.exists(x, has(self.rules.response.success.headers[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.dynamicMetadata) || !self.rules.response.success.dynamicMetadata.exists(x, has(self.rules.response.success.dynamicMetadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.callbacks) || !self.rules.callbacks.exists(x, has(self.rules.callbacks[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// RouteSelectors - explicit default validation +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.routeSelectors)",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) || !has(self.defaults.rules.authentication) || !self.defaults.rules.authentication.exists(x, has(self.defaults.rules.authentication[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) || !has(self.defaults.rules.metadata) || !self.defaults.rules.metadata.exists(x, has(self.defaults.rules.metadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) || !has(self.defaults.rules.authorization) || !self.defaults.rules.authorization.exists(x, has(self.defaults.rules.authorization[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) || !has(self.defaults.rules.response) || !has(self.defaults.rules.response.success) || !has(self.defaults.rules.response.success.headers) || !self.defaults.rules.response.success.headers.exists(x, has(self.defaults.rules.response.success.headers[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) || !has(self.defaults.rules.response) || !has(self.defaults.rules.response.success) || !has(self.defaults.rules.response.success.dynamicMetadata) || !self.defaults.rules.response.success.dynamicMetadata.exists(x, has(self.defaults.rules.response.success.dynamicMetadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) || !has(self.defaults.rules.callbacks) || !self.defaults.rules.callbacks.exists(x, has(self.defaults.rules.callbacks[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" +// Mutual Exclusivity Validation +// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && (has(self.routeSelectors) || has(self.patterns) || has(self.when) || has(self.rules)))",message="Implicit and explicit defaults are mutually exclusive" type AuthPolicySpec struct { // TargetRef identifies an API object to apply policy to. // +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'",message="Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'" @@ -144,7 +155,7 @@ type AuthPolicySpec struct { // Defaults define explicit default values for this policy and for policies inheriting this policy. // Defaults are mutually exclusive with implicit defaults defined by CommonSpec. // +optional - Defaults *CommonSpec `json:"defaults"` + Defaults *CommonSpec `json:"defaults,omitempty"` // CommonSpec defines implicit default values for this policy and for policies inheriting this policy. // CommonSpec is mutually exclusive with explicit defaults defined by Defaults. @@ -173,16 +184,7 @@ type CommonSpec struct { // The auth rules of the policy. // See Authorino's AuthConfig CRD for more details. - AuthScheme AuthSchemeSpec `json:"rules,omitempty"` -} - -// GetRouteSelectors returns the top-level route selectors of the auth scheme. -// impl: RouteSelectorsGetter -func (s AuthPolicySpec) GetRouteSelectors() []RouteSelector { - if s.Defaults != nil { - return s.Defaults.RouteSelectors - } - return s.RouteSelectors + AuthScheme *AuthSchemeSpec `json:"rules,omitempty"` } type AuthPolicyStatus struct { @@ -278,6 +280,10 @@ func (ap *AuthPolicy) GetRulesHostnames() (ruleHosts []string) { } appendCommonSpecRuleHosts := func(c CommonSpec) { + if c.AuthScheme == nil { + return + } + for _, config := range c.AuthScheme.Authentication { appendRuleHosts(config) } @@ -300,7 +306,7 @@ func (ap *AuthPolicy) GetRulesHostnames() (ruleHosts []string) { } } - appendRuleHosts(ap.Spec) + appendRuleHosts(ap) appendCommonSpecRuleHosts(ap.GetCommonSpec()) return @@ -334,10 +340,16 @@ func (ap *AuthPolicy) GetConditions() []authorinoapi.PatternExpressionOrRef { return ap.GetCommonSpec().Conditions } -func (ap *AuthPolicy) GetAuthScheme() AuthSchemeSpec { +func (ap *AuthPolicy) GetAuthScheme() *AuthSchemeSpec { return ap.GetCommonSpec().AuthScheme } +// GetRouteSelectors returns the top-level route selectors of the auth scheme. +// impl: RouteSelectorsGetter +func (ap *AuthPolicy) GetRouteSelectors() []RouteSelector { + return ap.GetCommonSpec().RouteSelectors +} + //+kubebuilder:object:root=true // AuthPolicyList contains a list of AuthPolicy diff --git a/api/v1beta2/authpolicy_types_test.go b/api/v1beta2/authpolicy_types_test.go index 9bd32f349..d127eafbf 100644 --- a/api/v1beta2/authpolicy_types_test.go +++ b/api/v1beta2/authpolicy_types_test.go @@ -32,13 +32,13 @@ func TestCommonAuthRuleSpecGetRouteSelectors(t *testing.T) { } func TestAuthPolicySpecGetRouteSelectors(t *testing.T) { - spec := &AuthPolicySpec{} - if spec.GetRouteSelectors() != nil { + p := &AuthPolicy{} + if p.GetRouteSelectors() != nil { t.Errorf("Expected nil route selectors") } routeSelector := testBuildRouteSelector() - spec.RouteSelectors = []RouteSelector{routeSelector} - result := spec.GetRouteSelectors() + p.Spec.RouteSelectors = []RouteSelector{routeSelector} + result := p.GetRouteSelectors() if len(result) != 1 { t.Errorf("Expected 1 route selector, got %d", len(result)) } @@ -128,6 +128,7 @@ func TestAuthPolicyGetRulesHostnames(t *testing.T) { t.Errorf("Expected hostname to be %s, got %s", expected, result[1]) } // + 1 authentication route selector with 1 hostname + policy.Spec.AuthScheme = &AuthSchemeSpec{} policy.Spec.AuthScheme.Authentication = map[string]AuthenticationSpec{ "my-authn": { CommonAuthRuleSpec: CommonAuthRuleSpec{ @@ -262,7 +263,7 @@ func TestAuthPolicyValidate(t *testing.T) { Namespace: ptr.To(gatewayapiv1.Namespace("other-namespace")), }, CommonSpec: CommonSpec{ - AuthScheme: AuthSchemeSpec{ + AuthScheme: &AuthSchemeSpec{ Authentication: map[string]AuthenticationSpec{ "my-rule": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index ae44ce26b..5e48d398f 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -284,7 +284,11 @@ func (in *CommonSpec) DeepCopyInto(out *CommonSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - in.AuthScheme.DeepCopyInto(&out.AuthScheme) + if in.AuthScheme != nil { + in, out := &in.AuthScheme, &out.AuthScheme + *out = new(AuthSchemeSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonSpec. diff --git a/bundle/manifests/kuadrant.io_authpolicies.yaml b/bundle/manifests/kuadrant.io_authpolicies.yaml index 172cace67..9391b7035 100644 --- a/bundle/manifests/kuadrant.io_authpolicies.yaml +++ b/bundle/manifests/kuadrant.io_authpolicies.yaml @@ -65,6 +65,10 @@ spec: metadata: type: object spec: + description: |- + RouteSelectors - implicit default validation + RouteSelectors - explicit default validation + Mutual Exclusivity Validation properties: defaults: description: |- @@ -8686,25 +8690,56 @@ spec: - message: route selectors not supported when targeting a Gateway rule: self.targetRef.kind != 'Gateway' || !has(self.routeSelectors) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.authentication) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.authentication) || !self.rules.authentication.exists(x, has(self.rules.authentication[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.metadata) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.metadata) || !self.rules.metadata.exists(x, has(self.rules.metadata[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.authorization) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.authorization) || !self.rules.authorization.exists(x, has(self.rules.authorization[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.response) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.headers) || !self.rules.response.success.headers.exists(x, has(self.rules.response.success.headers[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.response) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.dynamicMetadata) || !self.rules.response.success.dynamicMetadata.exists(x, has(self.rules.response.success.dynamicMetadata[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.callbacks) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.callbacks) || !self.rules.callbacks.exists(x, has(self.rules.callbacks[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.routeSelectors) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.authentication) || !self.defaults.rules.authentication.exists(x, + has(self.defaults.rules.authentication[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.metadata) || !self.defaults.rules.metadata.exists(x, + has(self.defaults.rules.metadata[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.authorization) || !self.defaults.rules.authorization.exists(x, + has(self.defaults.rules.authorization[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.response) || !has(self.defaults.rules.response.success) + || !has(self.defaults.rules.response.success.headers) || !self.defaults.rules.response.success.headers.exists(x, + has(self.defaults.rules.response.success.headers[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.response) || !has(self.defaults.rules.response.success) + || !has(self.defaults.rules.response.success.dynamicMetadata) || !self.defaults.rules.response.success.dynamicMetadata.exists(x, + has(self.defaults.rules.response.success.dynamicMetadata[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.callbacks) || !self.defaults.rules.callbacks.exists(x, + has(self.defaults.rules.callbacks[x].routeSelectors)) + - message: Implicit and explicit defaults are mutually exclusive + rule: '!(has(self.defaults) && (has(self.routeSelectors) || has(self.patterns) + || has(self.when) || has(self.rules)))' status: properties: conditions: diff --git a/config/crd/bases/kuadrant.io_authpolicies.yaml b/config/crd/bases/kuadrant.io_authpolicies.yaml index 995961fe5..a300596b0 100644 --- a/config/crd/bases/kuadrant.io_authpolicies.yaml +++ b/config/crd/bases/kuadrant.io_authpolicies.yaml @@ -64,6 +64,10 @@ spec: metadata: type: object spec: + description: |- + RouteSelectors - implicit default validation + RouteSelectors - explicit default validation + Mutual Exclusivity Validation properties: defaults: description: |- @@ -8685,25 +8689,56 @@ spec: - message: route selectors not supported when targeting a Gateway rule: self.targetRef.kind != 'Gateway' || !has(self.routeSelectors) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.authentication) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.authentication) || !self.rules.authentication.exists(x, has(self.rules.authentication[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.metadata) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.metadata) || !self.rules.metadata.exists(x, has(self.rules.metadata[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.authorization) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.authorization) || !self.rules.authorization.exists(x, has(self.rules.authorization[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.response) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.headers) || !self.rules.response.success.headers.exists(x, has(self.rules.response.success.headers[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.response) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.response) || !has(self.rules.response.success) || !has(self.rules.response.success.dynamicMetadata) || !self.rules.response.success.dynamicMetadata.exists(x, has(self.rules.response.success.dynamicMetadata[x].routeSelectors)) - message: route selectors not supported when targeting a Gateway - rule: self.targetRef.kind != 'Gateway' || !has(self.rules.callbacks) + rule: self.targetRef.kind != 'Gateway' || !has(self.rules) || !has(self.rules.callbacks) || !self.rules.callbacks.exists(x, has(self.rules.callbacks[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.routeSelectors) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.authentication) || !self.defaults.rules.authentication.exists(x, + has(self.defaults.rules.authentication[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.metadata) || !self.defaults.rules.metadata.exists(x, + has(self.defaults.rules.metadata[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.authorization) || !self.defaults.rules.authorization.exists(x, + has(self.defaults.rules.authorization[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.response) || !has(self.defaults.rules.response.success) + || !has(self.defaults.rules.response.success.headers) || !self.defaults.rules.response.success.headers.exists(x, + has(self.defaults.rules.response.success.headers[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.response) || !has(self.defaults.rules.response.success) + || !has(self.defaults.rules.response.success.dynamicMetadata) || !self.defaults.rules.response.success.dynamicMetadata.exists(x, + has(self.defaults.rules.response.success.dynamicMetadata[x].routeSelectors)) + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.defaults) || !has(self.defaults.rules) + || !has(self.defaults.rules.callbacks) || !self.defaults.rules.callbacks.exists(x, + has(self.defaults.rules.callbacks[x].routeSelectors)) + - message: Implicit and explicit defaults are mutually exclusive + rule: '!(has(self.defaults) && (has(self.routeSelectors) || has(self.patterns) + || has(self.when) || has(self.rules)))' status: properties: conditions: diff --git a/controllers/authpolicy_authconfig.go b/controllers/authpolicy_authconfig.go index 53f06cfa6..59e56e63a 100644 --- a/controllers/authpolicy_authconfig.go +++ b/controllers/authpolicy_authconfig.go @@ -117,7 +117,7 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au } // top-level conditions - topLevelConditionsFromRouteSelectors, err := authorinoConditionsFromRouteSelectors(route, ap.Spec) + topLevelConditionsFromRouteSelectors, err := authorinoConditionsFromRouteSelectors(route, ap) if err != nil { return nil, err } diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index 569381c32..260eeca50 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -463,7 +463,7 @@ var _ = Describe("AuthPolicy controller", func() { }, }, } - policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "jwt": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ @@ -1270,29 +1270,29 @@ var _ = Describe("AuthPolicy CEL Validations", func() { AfterEach(DeleteNamespaceCallback(&testNamespace)) - Context("Spec TargetRef Validations", func() { - policyFactory := func(mutateFns ...func(policy *api.AuthPolicy)) *api.AuthPolicy { - policy := &api.AuthPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - Namespace: testNamespace, - }, - Spec: api.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: "my-target", - }, + policyFactory := func(mutateFns ...func(policy *api.AuthPolicy)) *api.AuthPolicy { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: "my-target", }, - } - - for _, mutateFn := range mutateFns { - mutateFn(policy) - } + }, + } - return policy + for _, mutateFn := range mutateFns { + mutateFn(policy) } + return policy + } + + Context("Spec TargetRef Validations", func() { It("Valid policy targeting HTTPRoute", func() { policy := policyFactory() err := k8sClient.Create(context.Background(), policy) @@ -1326,6 +1326,89 @@ var _ = Describe("AuthPolicy CEL Validations", func() { }) }) + Context("Defaults mutual exclusivity validation", func() { + It("Valid when only implicit defaults are used", func(ctx SpecContext) { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.AuthScheme = testBasicAuthScheme() + }) + Expect(k8sClient.Create(ctx, policy)).To(Succeed()) + }) + + It("Valid when only explicit defaults are used", func(ctx SpecContext) { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{ + AuthScheme: testBasicAuthScheme(), + } + }) + Expect(k8sClient.Create(ctx, policy)).To(Succeed()) + }) + + It("Invalid when both implicit and explicit defaults are used - authScheme", func(ctx SpecContext) { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.AuthScheme = testBasicAuthScheme() + }) + err := k8sClient.Create(ctx, policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Implicit and explicit defaults are mutually exclusive")).To(BeTrue()) + }) + + It("Invalid when both implicit and explicit defaults are used - routeSelectors", func(ctx SpecContext) { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.RouteSelectors = []api.RouteSelector{ + { + Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, + Matches: []gatewayapiv1.HTTPRouteMatch{ + { + Path: &gatewayapiv1.HTTPPathMatch{ + Value: ptr.To("/foo"), + }, + }, + }, + }, + } + }) + err := k8sClient.Create(ctx, policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Implicit and explicit defaults are mutually exclusive")).To(BeTrue()) + }) + + It("Invalid when both implicit and explicit defaults are used - namedPatterns", func(ctx SpecContext) { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.NamedPatterns = map[string]authorinoapi.PatternExpressions{ + "internal-source": []authorinoapi.PatternExpression{ + { + Selector: "source.ip", + Operator: authorinoapi.PatternExpressionOperator("matches"), + Value: `192\.168\..*`, + }, + }, + } + }) + err := k8sClient.Create(ctx, policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Implicit and explicit defaults are mutually exclusive")).To(BeTrue()) + }) + + It("Invalid when both implicit and explicit defaults are used - conditions", func(ctx SpecContext) { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Conditions = []authorinoapi.PatternExpressionOrRef{ + { + PatternRef: authorinoapi.PatternRef{ + Name: "internal-source", + }, + }, + } + }) + err := k8sClient.Create(ctx, policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Implicit and explicit defaults are mutually exclusive")).To(BeTrue()) + }) + }) + Context("Route Selector Validation", func() { const ( gateWayRouteSelectorErrorMessage = "route selectors not supported when targeting a Gateway" @@ -1371,6 +1454,17 @@ var _ = Describe("AuthPolicy CEL Validations", func() { } It("invalid usage of top-level route selectors with a gateway targetRef", func() { policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.RouteSelectors = routeSelectors + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of top-level route selectors with a gateway targetRef - defaults", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} policy.Spec.Defaults.RouteSelectors = routeSelectors }) @@ -1381,7 +1475,29 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authentication", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.AuthScheme = &api.AuthSchemeSpec{ + Authentication: map[string]api.AuthenticationSpec{ + "my-rule": { + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + }, + }, + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + } + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - authentication - defaults", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "my-rule": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ @@ -1402,7 +1518,24 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - metadata", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.AuthScheme = &api.AuthSchemeSpec{ + Metadata: map[string]api.MetadataSpec{ + "my-metadata": { + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + } + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - metadata - defaults", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Metadata: map[string]api.MetadataSpec{ "my-metadata": { CommonAuthRuleSpec: commonAuthRuleSpec, @@ -1418,7 +1551,24 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authorization", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.AuthScheme = &api.AuthSchemeSpec{ + Authorization: map[string]api.AuthorizationSpec{ + "my-authZ": { + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + } + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - authorization - defaults", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Authorization: map[string]api.AuthorizationSpec{ "my-authZ": { CommonAuthRuleSpec: commonAuthRuleSpec, @@ -1434,7 +1584,30 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success headers", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.AuthScheme = &api.AuthSchemeSpec{ + Response: &api.ResponseSpec{ + Success: api.WrappedSuccessResponseSpec{ + Headers: map[string]api.HeaderSuccessResponseSpec{ + "header": { + SuccessResponseSpec: api.SuccessResponseSpec{ + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + }, + } + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - response success headers - defaults", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ Headers: map[string]api.HeaderSuccessResponseSpec{ @@ -1456,7 +1629,29 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + Response: &api.ResponseSpec{ + Success: api.WrappedSuccessResponseSpec{ + DynamicMetadata: map[string]api.SuccessResponseSpec{ + "header": { + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + } + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata - defaults", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ DynamicMetadata: map[string]api.SuccessResponseSpec{ @@ -1476,7 +1671,31 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - callbacks", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.AuthScheme = api.AuthSchemeSpec{ + policy.Spec.AuthScheme = &api.AuthSchemeSpec{ + Callbacks: map[string]api.CallbackSpec{ + "callback": { + CallbackSpec: authorinoapi.CallbackSpec{ + CallbackMethodSpec: authorinoapi.CallbackMethodSpec{ + Http: &authorinoapi.HttpEndpointSpec{ + Url: "test.com", + }, + }, + }, + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + } + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - callbacks - defaults", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Callbacks: map[string]api.CallbackSpec{ "callback": { CallbackSpec: authorinoapi.CallbackSpec{ @@ -1499,8 +1718,8 @@ var _ = Describe("AuthPolicy CEL Validations", func() { }) }) -func testBasicAuthScheme() api.AuthSchemeSpec { - return api.AuthSchemeSpec{ +func testBasicAuthScheme() *api.AuthSchemeSpec { + return &api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "apiKey": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ diff --git a/controllers/authpolicy_istio_authorizationpolicy.go b/controllers/authpolicy_istio_authorizationpolicy.go index 54e6c75b5..8c18c0cae 100644 --- a/controllers/authpolicy_istio_authorizationpolicy.go +++ b/controllers/authpolicy_istio_authorizationpolicy.go @@ -193,7 +193,7 @@ func istioAuthorizationPolicyLabels(gwKey, apKey client.ObjectKey) map[string]st // If the route selectors specified in the policy do not match any route rules, an error is returned. func istioAuthorizationPolicyRules(ap *api.AuthPolicy, route *gatewayapiv1.HTTPRoute) ([]*istiosecurity.Rule, error) { // use only the top level route selectors if defined - if topLevelRouteSelectors := ap.Spec.GetRouteSelectors(); len(topLevelRouteSelectors) > 0 { + if topLevelRouteSelectors := ap.GetRouteSelectors(); len(topLevelRouteSelectors) > 0 { return istioAuthorizationPolicyRulesFromRouteSelectors(route, topLevelRouteSelectors) } return istioAuthorizationPolicyRulesFromHTTPRoute(route), nil From bf908a26e8a9076704638b69a2498bf85743bbcc Mon Sep 17 00:00:00 2001 From: KevFan Date: Mon, 25 Mar 2024 13:52:59 +0000 Subject: [PATCH 06/10] refactor: AuthPolicyCommonSpec --- api/v1beta2/authpolicy_types.go | 20 ++-- api/v1beta2/authpolicy_types_test.go | 2 +- api/v1beta2/zz_generated.deepcopy.go | 104 +++++++++--------- .../manifests/kuadrant.io_authpolicies.yaml | 2 +- .../crd/bases/kuadrant.io_authpolicies.yaml | 2 +- controllers/authpolicy_controller_test.go | 28 ++--- 6 files changed, 79 insertions(+), 79 deletions(-) diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index dcf073039..f0fd27132 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -153,17 +153,17 @@ type AuthPolicySpec struct { TargetRef gatewayapiv1alpha2.PolicyTargetReference `json:"targetRef"` // Defaults define explicit default values for this policy and for policies inheriting this policy. - // Defaults are mutually exclusive with implicit defaults defined by CommonSpec. + // Defaults are mutually exclusive with implicit defaults defined by AuthPolicyCommonSpec. // +optional - Defaults *CommonSpec `json:"defaults,omitempty"` + Defaults *AuthPolicyCommonSpec `json:"defaults,omitempty"` - // CommonSpec defines implicit default values for this policy and for policies inheriting this policy. - // CommonSpec is mutually exclusive with explicit defaults defined by Defaults. - CommonSpec `json:""` + // AuthPolicyCommonSpec defines implicit default values for this policy and for policies inheriting this policy. + // AuthPolicyCommonSpec is mutually exclusive with explicit defaults defined by Defaults. + AuthPolicyCommonSpec `json:""` } -// CommonSpec contains common shared fields for defaults and overrides -type CommonSpec struct { +// AuthPolicyCommonSpec contains common shared fields for defaults and overrides +type AuthPolicyCommonSpec struct { // Top-level route selectors. // If present, the elements will be used to select HTTPRoute rules that, when activated, trigger the external authorization service. // At least one selected HTTPRoute rule must match to trigger the AuthPolicy. @@ -279,7 +279,7 @@ func (ap *AuthPolicy) GetRulesHostnames() (ruleHosts []string) { } } - appendCommonSpecRuleHosts := func(c CommonSpec) { + appendCommonSpecRuleHosts := func(c AuthPolicyCommonSpec) { if c.AuthScheme == nil { return } @@ -324,12 +324,12 @@ func (ap *AuthPolicy) DirectReferenceAnnotationName() string { return AuthPolicyDirectReferenceAnnotationName } -func (ap *AuthPolicy) GetCommonSpec() CommonSpec { +func (ap *AuthPolicy) GetCommonSpec() AuthPolicyCommonSpec { if ap.Spec.Defaults != nil { return *ap.Spec.Defaults } - return ap.Spec.CommonSpec + return ap.Spec.AuthPolicyCommonSpec } func (ap *AuthPolicy) GetNamedPatterns() map[string]authorinoapi.PatternExpressions { diff --git a/api/v1beta2/authpolicy_types_test.go b/api/v1beta2/authpolicy_types_test.go index d127eafbf..b4fe57640 100644 --- a/api/v1beta2/authpolicy_types_test.go +++ b/api/v1beta2/authpolicy_types_test.go @@ -262,7 +262,7 @@ func TestAuthPolicyValidate(t *testing.T) { Name: "my-route", Namespace: ptr.To(gatewayapiv1.Namespace("other-namespace")), }, - CommonSpec: CommonSpec{ + AuthPolicyCommonSpec: AuthPolicyCommonSpec{ AuthScheme: &AuthSchemeSpec{ Authentication: map[string]AuthenticationSpec{ "my-rule": { diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 5e48d398f..cfb368da2 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -54,6 +54,56 @@ func (in *AuthPolicy) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthPolicyCommonSpec) DeepCopyInto(out *AuthPolicyCommonSpec) { + *out = *in + if in.RouteSelectors != nil { + in, out := &in.RouteSelectors, &out.RouteSelectors + *out = make([]RouteSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NamedPatterns != nil { + in, out := &in.NamedPatterns, &out.NamedPatterns + *out = make(map[string]apiv1beta2.PatternExpressions, len(*in)) + for key, val := range *in { + var outVal []apiv1beta2.PatternExpression + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make(apiv1beta2.PatternExpressions, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]apiv1beta2.PatternExpressionOrRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AuthScheme != nil { + in, out := &in.AuthScheme, &out.AuthScheme + *out = new(AuthSchemeSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthPolicyCommonSpec. +func (in *AuthPolicyCommonSpec) DeepCopy() *AuthPolicyCommonSpec { + if in == nil { + return nil + } + out := new(AuthPolicyCommonSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthPolicyList) DeepCopyInto(out *AuthPolicyList) { *out = *in @@ -92,10 +142,10 @@ func (in *AuthPolicySpec) DeepCopyInto(out *AuthPolicySpec) { in.TargetRef.DeepCopyInto(&out.TargetRef) if in.Defaults != nil { in, out := &in.Defaults, &out.Defaults - *out = new(CommonSpec) + *out = new(AuthPolicyCommonSpec) (*in).DeepCopyInto(*out) } - in.CommonSpec.DeepCopyInto(&out.CommonSpec) + in.AuthPolicyCommonSpec.DeepCopyInto(&out.AuthPolicyCommonSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthPolicySpec. @@ -251,56 +301,6 @@ func (in *CommonAuthRuleSpec) DeepCopy() *CommonAuthRuleSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CommonSpec) DeepCopyInto(out *CommonSpec) { - *out = *in - if in.RouteSelectors != nil { - in, out := &in.RouteSelectors, &out.RouteSelectors - *out = make([]RouteSelector, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.NamedPatterns != nil { - in, out := &in.NamedPatterns, &out.NamedPatterns - *out = make(map[string]apiv1beta2.PatternExpressions, len(*in)) - for key, val := range *in { - var outVal []apiv1beta2.PatternExpression - if val == nil { - (*out)[key] = nil - } else { - inVal := (*in)[key] - in, out := &inVal, &outVal - *out = make(apiv1beta2.PatternExpressions, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]apiv1beta2.PatternExpressionOrRef, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.AuthScheme != nil { - in, out := &in.AuthScheme, &out.AuthScheme - *out = new(AuthSchemeSpec) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonSpec. -func (in *CommonSpec) DeepCopy() *CommonSpec { - if in == nil { - return nil - } - out := new(CommonSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HeaderSuccessResponseSpec) DeepCopyInto(out *HeaderSuccessResponseSpec) { *out = *in diff --git a/bundle/manifests/kuadrant.io_authpolicies.yaml b/bundle/manifests/kuadrant.io_authpolicies.yaml index 9391b7035..a6bac0ae0 100644 --- a/bundle/manifests/kuadrant.io_authpolicies.yaml +++ b/bundle/manifests/kuadrant.io_authpolicies.yaml @@ -73,7 +73,7 @@ spec: defaults: description: |- Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by CommonSpec. + Defaults are mutually exclusive with implicit defaults defined by AuthPolicyCommonSpec. properties: patterns: additionalProperties: diff --git a/config/crd/bases/kuadrant.io_authpolicies.yaml b/config/crd/bases/kuadrant.io_authpolicies.yaml index a300596b0..9c5e0211c 100644 --- a/config/crd/bases/kuadrant.io_authpolicies.yaml +++ b/config/crd/bases/kuadrant.io_authpolicies.yaml @@ -72,7 +72,7 @@ spec: defaults: description: |- Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by CommonSpec. + Defaults are mutually exclusive with implicit defaults defined by AuthPolicyCommonSpec. properties: patterns: additionalProperties: diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index 260eeca50..1da646aed 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -66,7 +66,7 @@ var _ = Describe("AuthPolicy controller", func() { Name: testHTTPRouteName, Namespace: ptr.To(gatewayapiv1.Namespace(testNamespace)), }, - Defaults: &api.CommonSpec{ + Defaults: &api.AuthPolicyCommonSpec{ AuthScheme: testBasicAuthScheme(), }, }, @@ -1336,7 +1336,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Valid when only explicit defaults are used", func(ctx SpecContext) { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{ + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{ AuthScheme: testBasicAuthScheme(), } }) @@ -1345,7 +1345,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Invalid when both implicit and explicit defaults are used - authScheme", func(ctx SpecContext) { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.AuthScheme = testBasicAuthScheme() }) err := k8sClient.Create(ctx, policy) @@ -1355,7 +1355,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Invalid when both implicit and explicit defaults are used - routeSelectors", func(ctx SpecContext) { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.RouteSelectors = []api.RouteSelector{ { Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, @@ -1376,7 +1376,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Invalid when both implicit and explicit defaults are used - namedPatterns", func(ctx SpecContext) { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.NamedPatterns = map[string]authorinoapi.PatternExpressions{ "internal-source": []authorinoapi.PatternExpression{ { @@ -1394,7 +1394,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Invalid when both implicit and explicit defaults are used - conditions", func(ctx SpecContext) { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Conditions = []authorinoapi.PatternExpressionOrRef{ { PatternRef: authorinoapi.PatternRef{ @@ -1464,7 +1464,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of top-level route selectors with a gateway targetRef - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.RouteSelectors = routeSelectors }) @@ -1496,7 +1496,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authentication - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "my-rule": { @@ -1534,7 +1534,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - metadata - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Metadata: map[string]api.MetadataSpec{ "my-metadata": { @@ -1567,7 +1567,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authorization - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Authorization: map[string]api.AuthorizationSpec{ "my-authZ": { @@ -1606,7 +1606,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success headers - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ @@ -1629,7 +1629,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ @@ -1650,7 +1650,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ @@ -1694,7 +1694,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - callbacks - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults = &api.CommonSpec{} + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ Callbacks: map[string]api.CallbackSpec{ "callback": { From 00a034317529e72ee09450faf3f0f62679b8cd80 Mon Sep 17 00:00:00 2001 From: KevFan Date: Tue, 26 Mar 2024 16:14:25 +0000 Subject: [PATCH 07/10] docs: update AuthPolicy doc with defaults field --- doc/reference/authpolicy.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/reference/authpolicy.md b/doc/reference/authpolicy.md index 7a8e0d85f..6efb5d8ee 100644 --- a/doc/reference/authpolicy.md +++ b/doc/reference/authpolicy.md @@ -12,6 +12,7 @@ - [SuccessResponseItem](#successresponseitem) - [CallbackRule](#callbackrule) - [NamedPattern](#namedpattern) + - [AuthPolicyCommonSpec](#authPolicyCommonSpec) - [AuthPolicyStatus](#authpolicystatus) - [ConditionSpec](#conditionspec) @@ -24,9 +25,20 @@ ## AuthPolicySpec +| **Field** | **Type** | **Required** | **Description** | +|------------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `targetRef` | [PolicyTargetReference](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.PolicyTargetReference) | Yes | Reference to a Kuberentes resource that the policy attaches to | +| `rules` | [AuthScheme](#authscheme) | No | Implicit default authentication/authorization rules | +| `routeSelectors` | [][RouteSelector](route-selectors.md#routeselector) | No | List of implicit default selectors of HTTPRouteRules whose matching rules activate the policy. At least one HTTPRouteRule must be selected to activate the policy. If omitted, all HTTPRouteRules of the targeted HTTPRoute activate the policy. Do not use it in policies targeting a Gateway. | +| `patterns` | Map | No | Implicit default named patterns of lists of `selector`, `operator` and `value` tuples, to be reused in `when` conditions and pattern-matching authorization rules. | +| `when` | [][PatternExpressionOrRef](https://docs.kuadrant.io/authorino/docs/features/#common-feature-conditions-when) | No | List of implicit default additional dynamic conditions (expressions) to activate the policy. Use it for filtering attributes that cannot be expressed in the targeted HTTPRoute's `spec.hostnames` and `spec.rules.matches` fields, or when targeting a Gateway. | +| `defaults` | [AuthPolicyCommonSpec](#authPolicyCommonSpec) | No | Explicit default definitions. This field is mutually exclusive with any of the implicit default definitions: `spec.rules`, `spec.routeSelectors`, `spec.patterns`, `spec.when` | + + +## AuthPolicyCommonSpec + | **Field** | **Type** | **Required** | **Description** | |------------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `targetRef` | [PolicyTargetReference](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.PolicyTargetReference) | Yes | Reference to a Kuberentes resource that the policy attaches to | | `rules` | [AuthScheme](#authscheme) | No | Authentication/authorization rules | | `routeSelectors` | [][RouteSelector](route-selectors.md#routeselector) | No | List of selectors of HTTPRouteRules whose matching rules activate the policy. At least one HTTPRouteRule must be selected to activate the policy. If omitted, all HTTPRouteRules of the targeted HTTPRoute activate the policy. Do not use it in policies targeting a Gateway. | | `patterns` | Map | No | Named patterns of lists of `selector`, `operator` and `value` tuples, to be reused in `when` conditions and pattern-matching authorization rules. | From 4e8d06efb9b27b29c00639e211e2ad9b0aba186c Mon Sep 17 00:00:00 2001 From: KevFan Date: Tue, 2 Apr 2024 16:44:01 +0100 Subject: [PATCH 08/10] refactor: remove unneccessary getters --- api/v1beta2/authpolicy_types.go | 38 +++++-------- api/v1beta2/authpolicy_types_test.go | 8 +-- controllers/authpolicy_authconfig.go | 37 ++++++++----- controllers/authpolicy_controller_test.go | 53 ++++++++++++------- .../authpolicy_istio_authorizationpolicy.go | 3 +- 5 files changed, 75 insertions(+), 64 deletions(-) diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index f0fd27132..5284c208e 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -187,6 +187,12 @@ type AuthPolicyCommonSpec struct { AuthScheme *AuthSchemeSpec `json:"rules,omitempty"` } +// GetRouteSelectors returns the top-level route selectors of the auth scheme. +// impl: RouteSelectorsGetter +func (c AuthPolicyCommonSpec) GetRouteSelectors() []RouteSelector { + return c.RouteSelectors +} + type AuthPolicyStatus struct { // ObservedGeneration reflects the generation of the most recently observed spec. // +optional @@ -279,7 +285,7 @@ func (ap *AuthPolicy) GetRulesHostnames() (ruleHosts []string) { } } - appendCommonSpecRuleHosts := func(c AuthPolicyCommonSpec) { + appendCommonSpecRuleHosts := func(c *AuthPolicyCommonSpec) { if c.AuthScheme == nil { return } @@ -306,8 +312,8 @@ func (ap *AuthPolicy) GetRulesHostnames() (ruleHosts []string) { } } - appendRuleHosts(ap) - appendCommonSpecRuleHosts(ap.GetCommonSpec()) + appendRuleHosts(ap.Spec.CommonSpec()) + appendCommonSpecRuleHosts(ap.Spec.CommonSpec()) return } @@ -324,30 +330,12 @@ func (ap *AuthPolicy) DirectReferenceAnnotationName() string { return AuthPolicyDirectReferenceAnnotationName } -func (ap *AuthPolicy) GetCommonSpec() AuthPolicyCommonSpec { - if ap.Spec.Defaults != nil { - return *ap.Spec.Defaults +func (ap *AuthPolicySpec) CommonSpec() *AuthPolicyCommonSpec { + if ap.Defaults != nil { + return ap.Defaults } - return ap.Spec.AuthPolicyCommonSpec -} - -func (ap *AuthPolicy) GetNamedPatterns() map[string]authorinoapi.PatternExpressions { - return ap.GetCommonSpec().NamedPatterns -} - -func (ap *AuthPolicy) GetConditions() []authorinoapi.PatternExpressionOrRef { - return ap.GetCommonSpec().Conditions -} - -func (ap *AuthPolicy) GetAuthScheme() *AuthSchemeSpec { - return ap.GetCommonSpec().AuthScheme -} - -// GetRouteSelectors returns the top-level route selectors of the auth scheme. -// impl: RouteSelectorsGetter -func (ap *AuthPolicy) GetRouteSelectors() []RouteSelector { - return ap.GetCommonSpec().RouteSelectors + return &ap.AuthPolicyCommonSpec } //+kubebuilder:object:root=true diff --git a/api/v1beta2/authpolicy_types_test.go b/api/v1beta2/authpolicy_types_test.go index b4fe57640..c0fb1df24 100644 --- a/api/v1beta2/authpolicy_types_test.go +++ b/api/v1beta2/authpolicy_types_test.go @@ -32,13 +32,13 @@ func TestCommonAuthRuleSpecGetRouteSelectors(t *testing.T) { } func TestAuthPolicySpecGetRouteSelectors(t *testing.T) { - p := &AuthPolicy{} - if p.GetRouteSelectors() != nil { + spec := &AuthPolicySpec{} + if spec.GetRouteSelectors() != nil { t.Errorf("Expected nil route selectors") } routeSelector := testBuildRouteSelector() - p.Spec.RouteSelectors = []RouteSelector{routeSelector} - result := p.GetRouteSelectors() + spec.RouteSelectors = []RouteSelector{routeSelector} + result := spec.GetRouteSelectors() if len(result) != 1 { t.Errorf("Expected 1 route selector, got %d", len(result)) } diff --git a/controllers/authpolicy_authconfig.go b/controllers/authpolicy_authconfig.go index 59e56e63a..dcfb03360 100644 --- a/controllers/authpolicy_authconfig.go +++ b/controllers/authpolicy_authconfig.go @@ -111,40 +111,47 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au // hosts authConfig.Spec.Hosts = hosts + commonSpec := ap.Spec.CommonSpec() + // named patterns - if namedPatterns := ap.GetNamedPatterns(); len(namedPatterns) > 0 { + if namedPatterns := commonSpec.NamedPatterns; len(namedPatterns) > 0 { authConfig.Spec.NamedPatterns = namedPatterns } // top-level conditions - topLevelConditionsFromRouteSelectors, err := authorinoConditionsFromRouteSelectors(route, ap) + topLevelConditionsFromRouteSelectors, err := authorinoConditionsFromRouteSelectors(route, commonSpec) if err != nil { return nil, err } if len(topLevelConditionsFromRouteSelectors) == 0 { topLevelConditionsFromRouteSelectors = authorinoConditionsFromHTTPRoute(route) } - if len(topLevelConditionsFromRouteSelectors) > 0 || len(ap.GetConditions()) > 0 { - authConfig.Spec.Conditions = append(ap.GetConditions(), topLevelConditionsFromRouteSelectors...) + if len(topLevelConditionsFromRouteSelectors) > 0 || len(commonSpec.Conditions) > 0 { + authConfig.Spec.Conditions = append(commonSpec.Conditions, topLevelConditionsFromRouteSelectors...) + } + + // return early if authScheme is nil + if commonSpec.AuthScheme == nil { + return authConfig, nil } // authentication - if authentication := ap.GetAuthScheme().Authentication; len(authentication) > 0 { + if authentication := commonSpec.AuthScheme.Authentication; len(authentication) > 0 { authConfig.Spec.Authentication = authorinoSpecsFromConfigs(authentication, func(config api.AuthenticationSpec) authorinoapi.AuthenticationSpec { return config.AuthenticationSpec }) } // metadata - if metadata := ap.GetAuthScheme().Metadata; len(metadata) > 0 { + if metadata := commonSpec.AuthScheme.Metadata; len(metadata) > 0 { authConfig.Spec.Metadata = authorinoSpecsFromConfigs(metadata, func(config api.MetadataSpec) authorinoapi.MetadataSpec { return config.MetadataSpec }) } // authorization - if authorization := ap.GetAuthScheme().Authorization; len(authorization) > 0 { + if authorization := commonSpec.AuthScheme.Authorization; len(authorization) > 0 { authConfig.Spec.Authorization = authorinoSpecsFromConfigs(authorization, func(config api.AuthorizationSpec) authorinoapi.AuthorizationSpec { return config.AuthorizationSpec }) } // response - if response := ap.GetAuthScheme().Response; response != nil { + if response := commonSpec.AuthScheme.Response; response != nil { authConfig.Spec.Response = &authorinoapi.ResponseSpec{ Unauthenticated: response.Unauthenticated, Unauthorized: response.Unauthorized, @@ -160,7 +167,7 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au } // callbacks - if callbacks := ap.GetAuthScheme().Callbacks; len(callbacks) > 0 { + if callbacks := commonSpec.AuthScheme.Callbacks; len(callbacks) > 0 { authConfig.Spec.Callbacks = authorinoSpecsFromConfigs(callbacks, func(config api.CallbackSpec) authorinoapi.CallbackSpec { return config.CallbackSpec }) } @@ -187,8 +194,10 @@ func authorinoSpecsFromConfigs[T, U any](configs map[string]U, extractAuthorinoS } func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gatewayapiv1.HTTPRoute, authConfig *authorinoapi.AuthConfig) (*authorinoapi.AuthConfig, error) { + commonSpec := ap.Spec.CommonSpec() + // authentication - for name, config := range ap.GetAuthScheme().Authentication { + for name, config := range commonSpec.AuthScheme.Authentication { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err @@ -202,7 +211,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // metadata - for name, config := range ap.GetAuthScheme().Metadata { + for name, config := range commonSpec.AuthScheme.Metadata { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err @@ -216,7 +225,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // authorization - for name, config := range ap.GetAuthScheme().Authorization { + for name, config := range commonSpec.AuthScheme.Authorization { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err @@ -230,7 +239,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // response - if response := ap.GetAuthScheme().Response; response != nil { + if response := commonSpec.AuthScheme.Response; response != nil { // response success headers for name, config := range response.Success.Headers { conditions, err := authorinoConditionsFromRouteSelectors(route, config) @@ -261,7 +270,7 @@ func mergeConditionsFromRouteSelectorsIntoConfigs(ap *api.AuthPolicy, route *gat } // callbacks - for name, config := range ap.GetAuthScheme().Callbacks { + for name, config := range commonSpec.AuthScheme.Callbacks { conditions, err := authorinoConditionsFromRouteSelectors(route, config) if err != nil { return nil, err diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index 1da646aed..ca0eec128 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -94,7 +94,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = testGatewayName - policy.Spec.Defaults.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" + policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) err := k8sClient.Create(context.Background(), policy) @@ -314,7 +314,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Rejects policy with only unmatching top-level route selectors while trying to configure the gateway", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.RouteSelectors = []api.RouteSelector{ + policy.Spec.CommonSpec().RouteSelectors = []api.RouteSelector{ { // does not select any HTTPRouteRule Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ { @@ -358,7 +358,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Rejects policy with only unmatching config-level route selectors post-configuring the gateway", func() { policy := policyFactory() - config := policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] + config := policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"] config.RouteSelectors = []api.RouteSelector{ { // does not select any HTTPRouteRule Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ @@ -368,7 +368,7 @@ var _ = Describe("AuthPolicy controller", func() { }, }, } - policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] = config + policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"] = config err := k8sClient.Create(context.Background(), policy) logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(policy).String(), "error", err) @@ -440,7 +440,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Maps to all fields of the AuthConfig", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.NamedPatterns = map[string]authorinoapi.PatternExpressions{ + policy.Spec.CommonSpec().NamedPatterns = map[string]authorinoapi.PatternExpressions{ "internal-source": []authorinoapi.PatternExpression{ { Selector: "source.ip", @@ -456,14 +456,14 @@ var _ = Describe("AuthPolicy controller", func() { }, }, } - policy.Spec.Defaults.Conditions = []authorinoapi.PatternExpressionOrRef{ + policy.Spec.CommonSpec().Conditions = []authorinoapi.PatternExpressionOrRef{ { PatternRef: authorinoapi.PatternRef{ Name: "internal-source", }, }, } - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "jwt": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ @@ -670,6 +670,19 @@ var _ = Describe("AuthPolicy controller", func() { authConfigSpecAsJSON, _ := json.Marshal(authConfig.Spec) Expect(string(authConfigSpecAsJSON)).To(Equal(`{"hosts":["*.toystore.com"],"patterns":{"authz-and-rl-required":[{"selector":"source.ip","operator":"neq","value":"192.168.0.10"}],"internal-source":[{"selector":"source.ip","operator":"matches","value":"192\\.168\\..*"}]},"when":[{"patternRef":"internal-source"},{"any":[{"any":[{"all":[{"selector":"request.method","operator":"eq","value":"GET"},{"selector":"request.url_path","operator":"matches","value":"/toy.*"}]}]}]}],"authentication":{"jwt":{"when":[{"selector":"filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt","operator":"neq"}],"credentials":{"authorizationHeader":{}},"plain":{"selector":"filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt"}}},"metadata":{"user-groups":{"when":[{"selector":"auth.identity.admin","operator":"neq","value":"true"}],"http":{"url":"http://user-groups/username={auth.identity.username}","method":"GET","contentType":"application/x-www-form-urlencoded","credentials":{"authorizationHeader":{}}}}},"authorization":{"admin-or-privileged":{"when":[{"patternRef":"authz-and-rl-required"}],"patternMatching":{"patterns":[{"any":[{"selector":"auth.identity.admin","operator":"eq","value":"true"},{"selector":"auth.metadata.user-groups","operator":"incl","value":"privileged"}]}]}}},"response":{"unauthenticated":{"message":{"value":"Missing verified JWT injected by the gateway"}},"unauthorized":{"message":{"value":"User must be admin or member of privileged group"}},"success":{"headers":{"x-username":{"when":[{"selector":"request.headers.x-propagate-username.@case:lower","operator":"matches","value":"1|yes|true"}],"plain":{"value":null,"selector":"auth.identity.username"}}},"dynamicMetadata":{"x-auth-data":{"when":[{"patternRef":"authz-and-rl-required"}],"json":{"properties":{"groups":{"value":null,"selector":"auth.metadata.user-groups"},"username":{"value":null,"selector":"auth.identity.username"}}}}}}},"callbacks":{"unauthorized-attempt":{"when":[{"patternRef":"authz-and-rl-required"},{"selector":"auth.authorization.admin-or-privileged","operator":"neq","value":"true"}],"http":{"url":"http://events/unauthorized","method":"POST","body":{"value":null,"selector":"\\{\"identity\":{auth.identity},\"request-id\":{request.id}\\}"},"contentType":"application/json","credentials":{"authorizationHeader":{}}}}}}`)) }) + + It("Succeeds when AuthScheme is not defined", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.CommonSpec().AuthScheme = nil + }) + + err := k8sClient.Create(context.Background(), policy) + logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(policy).String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + Eventually(isAuthPolicyAccepted(policy), 30*time.Second, 5*time.Second).Should(BeTrue()) + Eventually(isAuthPolicyEnforced(policy), 30*time.Second, 5*time.Second).Should(BeTrue()) + }) }) Context("Complex HTTPRoute with multiple rules and hostnames", func() { @@ -757,7 +770,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Attaches policy with top-level route selectors to the HTTPRoute", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.Defaults.RouteSelectors = []api.RouteSelector{ + policy.Spec.CommonSpec().RouteSelectors = []api.RouteSelector{ { // Selects: POST|DELETE *.admin.toystore.com/admin* Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ { @@ -862,7 +875,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Attaches policy with config-level route selectors to the HTTPRoute", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - config := policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] + config := policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"] config.RouteSelectors = []api.RouteSelector{ { // Selects: POST|DELETE *.admin.toystore.com/admin* Matches: []gatewayapiv1alpha2.HTTPRouteMatch{ @@ -876,7 +889,7 @@ var _ = Describe("AuthPolicy controller", func() { Hostnames: []gatewayapiv1.Hostname{"*.admin.toystore.com"}, }, } - policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] = config + policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"] = config }) err := k8sClient.Create(context.Background(), policy) @@ -977,7 +990,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Mixes route selectors into other conditions", func() { policy := policyFactory(func(policy *api.AuthPolicy) { - config := policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] + config := policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"] config.RouteSelectors = []api.RouteSelector{ { // Selects: GET /private* Matches: []gatewayapiv1.HTTPRouteMatch{ @@ -1000,7 +1013,7 @@ var _ = Describe("AuthPolicy controller", func() { }, }, } - policy.Spec.Defaults.AuthScheme.Authentication["apiKey"] = config + policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"] = config }) err := k8sClient.Create(context.Background(), policy) @@ -1465,7 +1478,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of top-level route selectors with a gateway targetRef - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.RouteSelectors = routeSelectors + policy.Spec.CommonSpec().RouteSelectors = routeSelectors }) err := k8sClient.Create(context.Background(), policy) @@ -1497,7 +1510,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authentication - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ "my-rule": { AuthenticationSpec: authorinoapi.AuthenticationSpec{ @@ -1535,7 +1548,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - metadata - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Metadata: map[string]api.MetadataSpec{ "my-metadata": { CommonAuthRuleSpec: commonAuthRuleSpec, @@ -1568,7 +1581,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - authorization - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Authorization: map[string]api.AuthorizationSpec{ "my-authZ": { CommonAuthRuleSpec: commonAuthRuleSpec, @@ -1607,7 +1620,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success headers - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ Headers: map[string]api.HeaderSuccessResponseSpec{ @@ -1630,7 +1643,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ DynamicMetadata: map[string]api.SuccessResponseSpec{ @@ -1651,7 +1664,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Response: &api.ResponseSpec{ Success: api.WrappedSuccessResponseSpec{ DynamicMetadata: map[string]api.SuccessResponseSpec{ @@ -1695,7 +1708,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("invalid usage of config-level route selectors with a gateway targetRef - callbacks - defaults", func() { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.Defaults.AuthScheme = &api.AuthSchemeSpec{ + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ Callbacks: map[string]api.CallbackSpec{ "callback": { CallbackSpec: authorinoapi.CallbackSpec{ diff --git a/controllers/authpolicy_istio_authorizationpolicy.go b/controllers/authpolicy_istio_authorizationpolicy.go index 8c18c0cae..2e5efeafa 100644 --- a/controllers/authpolicy_istio_authorizationpolicy.go +++ b/controllers/authpolicy_istio_authorizationpolicy.go @@ -192,8 +192,9 @@ func istioAuthorizationPolicyLabels(gwKey, apKey client.ObjectKey) map[string]st // If no rules are specified, the gateway will call external authorization for all requests. // If the route selectors specified in the policy do not match any route rules, an error is returned. func istioAuthorizationPolicyRules(ap *api.AuthPolicy, route *gatewayapiv1.HTTPRoute) ([]*istiosecurity.Rule, error) { + commonSpec := ap.Spec.CommonSpec() // use only the top level route selectors if defined - if topLevelRouteSelectors := ap.GetRouteSelectors(); len(topLevelRouteSelectors) > 0 { + if topLevelRouteSelectors := commonSpec.RouteSelectors; len(topLevelRouteSelectors) > 0 { return istioAuthorizationPolicyRulesFromRouteSelectors(route, topLevelRouteSelectors) } return istioAuthorizationPolicyRulesFromHTTPRoute(route), nil From 4bcd167c3da2cfe2a0ce173da9d6013a5b153e6c Mon Sep 17 00:00:00 2001 From: KevFan Date: Wed, 3 Apr 2024 11:51:23 +0100 Subject: [PATCH 09/10] refactor: reset number of root level route selectors & add integration tests --- api/v1beta2/authpolicy_types.go | 2 +- .../manifests/kuadrant.io_authpolicies.yaml | 4 +- .../crd/bases/kuadrant.io_authpolicies.yaml | 4 +- controllers/authpolicy_controller_test.go | 85 +++++++++++++++++-- 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index 5284c208e..759199fea 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -169,7 +169,7 @@ type AuthPolicyCommonSpec struct { // At least one selected HTTPRoute rule must match to trigger the AuthPolicy. // If no route selectors are specified, the AuthPolicy will be enforced at all requests to the protected routes. // +optional - // +kubebuilder:validation:MaxItems=8 + // +kubebuilder:validation:MaxItems=15 RouteSelectors []RouteSelector `json:"routeSelectors,omitempty"` // Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. diff --git a/bundle/manifests/kuadrant.io_authpolicies.yaml b/bundle/manifests/kuadrant.io_authpolicies.yaml index a6bac0ae0..3cf045372 100644 --- a/bundle/manifests/kuadrant.io_authpolicies.yaml +++ b/bundle/manifests/kuadrant.io_authpolicies.yaml @@ -400,7 +400,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 8 + maxItems: 15 type: array rules: description: |- @@ -4695,7 +4695,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 8 + maxItems: 15 type: array rules: description: |- diff --git a/config/crd/bases/kuadrant.io_authpolicies.yaml b/config/crd/bases/kuadrant.io_authpolicies.yaml index 9c5e0211c..766da133a 100644 --- a/config/crd/bases/kuadrant.io_authpolicies.yaml +++ b/config/crd/bases/kuadrant.io_authpolicies.yaml @@ -399,7 +399,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 8 + maxItems: 15 type: array rules: description: |- @@ -4694,7 +4694,7 @@ spec: maxItems: 8 type: array type: object - maxItems: 8 + maxItems: 15 type: array rules: description: |- diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index ca0eec128..c6c2996db 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -1428,19 +1428,17 @@ var _ = Describe("AuthPolicy CEL Validations", func() { ) var ( - routeSelectors = []api.RouteSelector{ - { - Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Value: ptr.To("/foo"), - }, + routeSelector = api.RouteSelector{ + Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, + Matches: []gatewayapiv1.HTTPRouteMatch{ + { + Path: &gatewayapiv1.HTTPPathMatch{ + Value: ptr.To("/foo"), }, }, }, } - + routeSelectors = []api.RouteSelector{routeSelector} commonAuthRuleSpec = api.CommonAuthRuleSpec{RouteSelectors: routeSelectors} ) @@ -1728,6 +1726,75 @@ var _ = Describe("AuthPolicy CEL Validations", func() { Expect(err).To(Not(BeNil())) Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) }) + + It("invalid usage of root level route selectors for HTTPRoute - max number is 15", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.TargetRef.Kind = "HTTPRoute" + policy.Spec.TargetRef.Name = "my-route" + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} + policy.Spec.CommonSpec().RouteSelectors = []api.RouteSelector{ + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + } + }) + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + logf.Log.V(1).Error(err, "theError") + Expect(strings.Contains(err.Error(), "Too many: 16: must have at most 15 items")).To(BeTrue()) + }) + + It("invalid usage of config level route selectors for HTTPRoute - max number is 8", func() { + policy := policyFactory(func(policy *api.AuthPolicy) { + policy.Spec.TargetRef.Kind = "HTTPRoute" + policy.Spec.TargetRef.Name = "my-route" + policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} + policy.Spec.CommonSpec().AuthScheme = &api.AuthSchemeSpec{ + Callbacks: map[string]api.CallbackSpec{ + "callback": { + CallbackSpec: authorinoapi.CallbackSpec{ + CallbackMethodSpec: authorinoapi.CallbackMethodSpec{ + Http: &authorinoapi.HttpEndpointSpec{ + Url: "test.com", + }, + }, + }, + CommonAuthRuleSpec: api.CommonAuthRuleSpec{ + RouteSelectors: []api.RouteSelector{ + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + routeSelector, + }, + }, + }, + }, + } + }) + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + logf.Log.V(1).Error(err, "theError") + Expect(strings.Contains(err.Error(), "Too many: 9: must have at most 8 items")).To(BeTrue()) + }) }) }) From bf701f83e99a4e2fa4df876b864a3be154310292 Mon Sep 17 00:00:00 2001 From: KevFan Date: Thu, 4 Apr 2024 11:11:30 +0100 Subject: [PATCH 10/10] refactor: remove debug log from new AP route selector integration tests --- controllers/authpolicy_controller_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index c6c2996db..b09790521 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -1752,9 +1752,8 @@ var _ = Describe("AuthPolicy CEL Validations", func() { } }) err := k8sClient.Create(context.Background(), policy) - Expect(err).To(Not(BeNil())) - logf.Log.V(1).Error(err, "theError") - Expect(strings.Contains(err.Error(), "Too many: 16: must have at most 15 items")).To(BeTrue()) + Expect(err).ToNot(BeNil()) + Expect(err.Error(), ContainSubstring("Too many: 16: must have at most 15 items")) }) It("invalid usage of config level route selectors for HTTPRoute - max number is 8", func() { @@ -1791,9 +1790,8 @@ var _ = Describe("AuthPolicy CEL Validations", func() { }) err := k8sClient.Create(context.Background(), policy) - Expect(err).To(Not(BeNil())) - logf.Log.V(1).Error(err, "theError") - Expect(strings.Contains(err.Error(), "Too many: 9: must have at most 8 items")).To(BeTrue()) + Expect(err).ToNot(BeNil()) + Expect(err.Error(), ContainSubstring("Too many: 9: must have at most 8 items")) }) }) })