From 8b7acddfee3dde903e9e5a0d1780d7773cabf47c Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Fri, 21 Jul 2023 09:18:49 +0200 Subject: [PATCH 01/35] AuthConfig v1beta2 CRD --- PROJECT | 3 + api/v1beta1/auth_config_types.go | 1 + api/v1beta2/auth_config_types.go | 877 ++++++ api/v1beta2/groupversion_info.go | 36 + api/v1beta2/zz_generated.deepcopy.go | 1367 +++++++++ .../authorino.kuadrant.io_authconfigs.yaml | 2442 +++++++++++++++++ install/manifests.yaml | 2442 +++++++++++++++++ 7 files changed, 7168 insertions(+) create mode 100644 api/v1beta2/auth_config_types.go create mode 100644 api/v1beta2/groupversion_info.go create mode 100644 api/v1beta2/zz_generated.deepcopy.go diff --git a/PROJECT b/PROJECT index 1c1b6fd9..1fd76fe1 100644 --- a/PROJECT +++ b/PROJECT @@ -6,6 +6,9 @@ resources: - group: config kind: AuthConfig version: v1beta1 +- group: config + kind: AuthConfig + version: v1beta2 version: 3-alpha plugins: go.sdk.operatorframework.io/v2-alpha: {} diff --git a/api/v1beta1/auth_config_types.go b/api/v1beta1/auth_config_types.go index 1e975ebf..f0535d99 100644 --- a/api/v1beta1/auth_config_types.go +++ b/api/v1beta1/auth_config_types.go @@ -774,6 +774,7 @@ func (s *AuthConfigStatus) Ready() bool { // AuthConfig is the schema for Authorino's AuthConfig API // +kubebuilder:object:root=true // +kubebuilder:subresource:status +// +kubebuilder:storageversion // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.summary.ready`,description="Ready for all hosts" // +kubebuilder:printcolumn:name="Hosts",type=string,JSONPath=`.status.summary.numHostsReady`,description="Number of hosts ready" // +kubebuilder:printcolumn:name="Authentication",type=integer,JSONPath=`.status.summary.numIdentitySources`,description="Number of trusted identity sources",priority=2 diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go new file mode 100644 index 00000000..30be9637 --- /dev/null +++ b/api/v1beta2/auth_config_types.go @@ -0,0 +1,877 @@ +/* +Copyright 2020 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta2 + +import ( + k8score "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sruntime "k8s.io/apimachinery/pkg/runtime" +) + +const ( + // The following constants are used to identify the different methods of authentication. + UnknownAuthenticationMethod AuthenticationMethod = iota + ApiKeyAuthentication + JwtAuthentication + OAuth2TokenIntrospectionAuthentication + KubernetesTokenReviewAuthentication + X509ClientCertificateAuthentication + PlainIdentityAuthentication + AnonymousAccessAuthentication + + // The following constants are used to identify the different methods of metadata fetching. + UnknownMetadataMethod MetadataMethod = iota + HttpMetadata + UserInfoMetadata + UmaResourceMetadata + + // The following constants are used to identify the different methods of authorization. + UnknownAuthorizationMethod AuthorizationMethod = iota + PatternMatchingAuthorization + OpaAuthorization + KubernetesSubjectAccessReviewAuthorization + SpiceDBAuthorization + + // The following constants are used to identify the different methods of auth response. + UnknownAuthResponseMethod AuthResponseMethod = iota + PlainAuthResponse + JsonAuthResponse + WristbandAuthResponse + + // The following constants are used to identify the different methods of callback functions. + UnknownCallbackMethod CallbackMethod = iota + HttpCallback + + // The following constants are used to identify the different types of credentials. + UnknownCredentialsType CredentialsType = iota + AuthorizationHeaderCredentials + CustomHeaderCredentials + QueryStringCredentials + CookieCredentials + + // Status conditions + StatusConditionAvailable StatusConditionType = "Available" + StatusConditionReady StatusConditionType = "Ready" + + // Status reasons + StatusReasonReconciling string = "Reconciling" + StatusReasonReconciled string = "Reconciled" + StatusReasonInvalidResource string = "Invalid" + StatusReasonHostsLinked string = "HostsLinked" + StatusReasonHostsNotLinked string = "HostsNotLinked" + StatusReasonCachingError string = "CachingError" + StatusReasonUnknown string = "Unknown" + + EvaluatorDefaultCacheTTL = 60 +) + +type AuthenticationMethod int8 +type MetadataMethod int8 +type AuthorizationMethod int8 +type AuthResponseMethod int8 +type CallbackMethod int8 +type CredentialsType int8 + +type StatusConditionType string + +// AuthConfig is the schema for Authorino's AuthConfig API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.summary.ready`,description="Ready for all hosts" +// +kubebuilder:printcolumn:name="Hosts",type=string,JSONPath=`.status.summary.numHostsReady`,description="Number of hosts ready" +// +kubebuilder:printcolumn:name="Authentication",type=integer,JSONPath=`.status.summary.numIdentitySources`,description="Number of trusted identity sources",priority=2 +// +kubebuilder:printcolumn:name="Metadata",type=integer,JSONPath=`.status.summary.numMetadataSources`,description="Number of external metadata sources",priority=2 +// +kubebuilder:printcolumn:name="Authorization",type=integer,JSONPath=`.status.summary.numAuthorizationPolicies`,description="Number of authorization policies",priority=2 +// +kubebuilder:printcolumn:name="Response",type=integer,JSONPath=`.status.summary.numResponseItems`,description="Number of items added to the authorization response",priority=2 +// +kubebuilder:printcolumn:name="Wristband",type=boolean,JSONPath=`.status.summary.festivalWristbandEnabled`,description="Whether issuing Festival Wristbands",priority=2 +type AuthConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AuthConfigSpec `json:"spec,omitempty"` + Status AuthConfigStatus `json:"status,omitempty"` +} + +// Specifies the desired state of the AuthConfig resource, i.e. the authencation/authorization scheme to be applied to protect the matching service hosts. +type AuthConfigSpec struct { + // The list of public host names of the services protected by this authentication/authorization scheme. + // Authorino uses the requested host to lookup for the corresponding authentication/authorization configs to enforce. + Hosts []string `json:"hosts"` + + // Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. + // +optional + NamedPatterns map[string]PatternExpressions `json:"patterns,omitempty"` + + // Overall conditions for the AuthConfig to be enforced. + // If omitted, the AuthConfig will be enforced at all requests. + // If present, all conditions must match for the AuthConfig to be enforced; otherwise, Authorino skips the AuthConfig and returns to the auth request with status OK. + // +optional + Conditions []PatternExpressionOrRef `json:"when,omitempty"` + + // Authentication configs. + // At least one config MUST evaluate to a valid identity object for the auth request to be successful. + // +optional + Authentication map[string]AuthenticationSpec `json:"authentication,omitempty"` + + // Metadata sources. + // Authorino fetches auth metadata as JSON from sources specified in this config. + // +optional + Metadata map[string]MetadataSpec `json:"metadata,omitempty"` + + // Authorization policies. + // All policies MUST evaluate to "allowed = true" for the auth request be successful. + // +optional + Authorization map[string]AuthorizationSpec `json:"authorization,omitempty"` + + // Response items. + // Authorino builds custom responses to the client of the auth request. + // +optional + Response *ResponseSpec `json:"response,omitempty"` + + // Callback functions. + // Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + // +optional + Callbacks map[string]CallbackSpec `json:"callbacks,omitempty"` +} + +type PatternExpressions []PatternExpression + +type PatternExpression struct { + // 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. + Selector string `json:"selector,omitempty"` + // 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) + Operator PatternExpressionOperator `json:"operator,omitempty"` + // 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. + Value string `json:"value,omitempty"` +} + +// +kubebuilder:validation:Enum:=eq;neq;incl;excl;matches +type PatternExpressionOperator string + +type PatternExpressionOrRef struct { + PatternExpression `json:",omitempty"` + PatternRef `json:",omitempty"` +} + +type PatternRef struct { + // Reference to a named set of pattern expressions + Name string `json:"patternRef,omitempty"` +} + +type NamedValuesOrSelectors map[string]ValueOrSelector + +type ValueOrSelector struct { + // Static value + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + Value k8sruntime.RawExtension `json:"value,omitempty"` + + // 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. + Selector string `json:"selector,omitempty"` +} + +type CommonEvaluatorSpec struct { + // Priority group of the config. + // All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + // +optional + // +kubebuilder:default:=0 + Priority int `json:"priority,omitempty"` + + // Whether this config should generate individual observability metrics + // +optional + // +kubebuilder:default:=false + Metrics bool `json:"metrics,omitempty"` + + // 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. + // +optional + Conditions []PatternExpressionOrRef `json:"when,omitempty"` + + // Caching options for the resolved object returned when applying this config. + // Omit it to avoid caching objects for this config. + // +optional + Cache *EvaluatorCaching `json:"cache,omitempty"` +} + +type EvaluatorCaching struct { + // Key used to store the entry in the cache. + // The resolved key must be unique within the scope of this particular config. + Key ValueOrSelector `json:"key"` + + // Duration (in seconds) of the external data in the cache before pulled again from the source. + // +optional + // +kubebuilder:default:=60 + TTL int `json:"ttl,omitempty"` +} + +type AuthenticationSpec struct { + CommonEvaluatorSpec `json:",omitempty"` + + // 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. + // +optional + Credentials Credentials `json:"credentials,omitempty"` + + // 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). + // +optional + Overrides ExtendedProperties `json:"overrides,omitempty"` + + // 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). + // +optional + Defaults ExtendedProperties `json:"defaults,omitempty"` + + AuthenticationMethodSpec `json:""` +} + +func (s *AuthenticationSpec) GetMethod() AuthenticationMethod { + if s.ApiKey != nil { + return ApiKeyAuthentication + } else if s.Jwt != nil { + return JwtAuthentication + } else if s.OAuth2TokenIntrospection != nil { + return OAuth2TokenIntrospectionAuthentication + } else if s.X509ClientCertificate != nil { + return X509ClientCertificateAuthentication + } else if s.KubernetesTokenReview != nil { + return KubernetesTokenReviewAuthentication + } else if s.Plain != nil { + return PlainIdentityAuthentication + } else if s.AnonymousAccess != nil { + return AnonymousAccessAuthentication + } + return UnknownAuthenticationMethod +} + +type Credentials struct { + AuthorizationHeader *Prefixed `json:"authorizationHeader,omitempty"` + CustomHeader *CustomHeader `json:"customHeader,omitempty"` + QueryString *Named `json:"queryString,omitempty"` + Cookie *Named `json:"cookie,omitempty"` +} + +func (c *Credentials) GetType() CredentialsType { + if c.AuthorizationHeader != nil { + return AuthorizationHeaderCredentials + } else if c.CustomHeader != nil { + return CustomHeaderCredentials + } else if c.QueryString != nil { + return QueryStringCredentials + } else if c.Cookie != nil { + return CookieCredentials + } + return UnknownCredentialsType +} + +type Named struct { + Name string `json:"name"` +} + +type Prefixed struct { + Prefix string `json:"prefix,omitempty"` +} + +type CustomHeader struct { + Named `json:""` + Prefixed `json:""` +} + +type ExtendedProperties NamedValuesOrSelectors + +type AuthenticationMethodSpec struct { + // Authentication based on API keys stored in Kubernetes secrets. + ApiKey *ApiKeyAuthenticationSpec `json:"apiKey,omitempty"` + // Authentication based on JWT tokens. + Jwt *JwtAuthenticationSpec `json:"jwt,omitempty"` + // Authentication by OAuth2 token introspection. + OAuth2TokenIntrospection *OAuth2TokenIntrospectionSpec `json:"oauth2Introspection,omitempty"` + // Authentication by Kubernetes token review. + KubernetesTokenReview *KubernetesTokenReviewSpec `json:"kubernetesTokenReview,omitempty"` + // 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. + X509ClientCertificate *X509ClientCertificateAuthenticationSpec `json:"x509,omitempty"` + // 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. + Plain *PlainIdentitySpec `json:"plain,omitempty"` + // Anonymous access. + AnonymousAccess *AnonymousAccessSpec `json:"anonymous,omitempty"` +} + +// Settings to select the API key Kubernetes secrets. +type ApiKeyAuthenticationSpec struct { + // Label selector used by Authorino to match secrets from the cluster storing valid credentials to authenticate to this service + Selector *metav1.LabelSelector `json:"selector"` + + // 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. + // +optional + // +kubebuilder:default:=false + AllNamespaces bool `json:"allNamespaces,omitempty"` +} + +// Settings to fetch the JSON Web Key Set (JWKS) for the JWT authentication. +type JwtAuthenticationSpec struct { + // URL of the JSON Web Ket Set (JWKS) endpoint. + // Use this if the issuer of the JWT tokens is not an OIDC provider or does not implement OIDC Discovery. + // +optional + JwksUrl string `json:"jwksUrl,omitempty"` + + // 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. + // +optional + IssuerUrl string `json:"issuerUrl"` + + // Decides how long to wait before refreshing the JWKS (in seconds). + // If omitted, Authorino will never refresh the JWKS. + // +optional + TTL int `json:"ttl,omitempty"` +} + +// Settings to perform the OAuth2 token introspection request. +type OAuth2TokenIntrospectionSpec struct { + // The full URL of the token introspection endpoint. + Url string `json:"endpoint"` + + // The token type hint for the token introspection. + // If omitted, it defaults to "access_token". + // +optional + TokenTypeHint string `json:"tokenTypeHint,omitempty"` + + // Reference to a Kubernetes secret in the same namespace, that stores client credentials to the OAuth2 server. + Credentials *k8score.LocalObjectReference `json:"credentialsRef"` +} + +// Parameters of the Kubernetes TokenReview request +type KubernetesTokenReviewSpec struct { + // 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. + // +optional + Audiences []string `json:"audiences,omitempty"` +} + +// Settings to authenticate clients by X.509 certificates. +type X509ClientCertificateAuthenticationSpec struct { + // Label selector used by Authorino to match secrets from the cluster storing trusted CA certificates to validate + // clients trying to authenticate to this service + Selector *metav1.LabelSelector `json:"selector"` + + // 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. + // +optional + // +kubebuilder:default:=false + AllNamespaces bool `json:"allNamespaces,omitempty"` +} + +// Settings to extract the identity object from the context. +type PlainIdentitySpec struct { + // 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. + Selector string `json:"selector"` +} + +type AnonymousAccessSpec struct{} + +type MetadataSpec struct { + CommonEvaluatorSpec `json:""` + MetadataMethodSpec `json:""` +} + +func (s *MetadataSpec) GetMethod() MetadataMethod { + if s.Http != nil { + return HttpMetadata + } else if s.UserInfo != nil { + return UserInfoMetadata + } else if s.Uma != nil { + return UmaResourceMetadata + } + return UnknownMetadataMethod +} + +type MetadataMethodSpec struct { + // External source of auth metadata via HTTP request + Http *HttpEndpointSpec `json:"http,omitempty"` + // OpendID Connect UserInfo linked to an OIDC authentication config specified in this same AuthConfig. + UserInfo *UserInfoMetadataSpec `json:"userInfo,omitempty"` + // User-Managed Access (UMA) source of resource data. + Uma *UmaMetadataSpec `json:"uma,omitempty"` +} + +// Settings of the external HTTP request +type HttpEndpointSpec struct { + // 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} + Url string `json:"url"` + + // 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. + // +optional + // +kubebuilder:default:=GET + Method *HttpMethod `json:"method,omitempty"` + + // 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). + // +optional + Body *ValueOrSelector `json:"body,omitempty"` + + // 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). + // +optional + Parameters NamedValuesOrSelectors `json:"bodyParameters,omitempty"` + + // 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'. + // +optional + // +kubebuilder:default:=application/x-www-form-urlencoded + ContentType HttpContentType `json:"contentType,omitempty"` + + // Custom headers in the HTTP request. + // +optional + Headers NamedValuesOrSelectors `json:"headers,omitempty"` + + // 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. + // +optional + SharedSecret *SecretKeyReference `json:"sharedSecretRef,omitempty"` + + // Authentication with the HTTP service by OAuth2 Client Credentials grant. + // +optional + OAuth2 *OAuth2ClientAuthentication `json:"oauth2,omitempty"` + + // 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. + // +optional + Credentials Credentials `json:"credentials,omitempty"` + + // Insecure HTTP connection (i.e. disables TLS verification) + Insecure bool `json:"insecure,omitempty"` +} + +// +kubebuilder:validation:Enum:=GET;POST;PUT;PATCH;DELETE;HEAD;OPTIONS;CONNECT;TRACE +type HttpMethod string + +// +kubebuilder:validation:Enum:=application/x-www-form-urlencoded;application/json +type HttpContentType string + +// Reference to a Kubernetes secret +type SecretKeyReference struct { + // The name of the secret in the Authorino's namespace to select from. + Name string `json:"name"` + + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` +} + +// Settings for OAuth2 client authentication with the external service +type OAuth2ClientAuthentication struct { + // Token endpoint URL of the OAuth2 resource server. + TokenUrl string `json:"tokenUrl"` + // OAuth2 Client ID. + ClientId string `json:"clientId"` + // Reference to a Kuberentes Secret key that stores that OAuth2 Client Secret. + ClientSecret SecretKeyReference `json:"clientSecretRef"` + // Optional scopes for the client credentials grant, if supported by he OAuth2 server. + Scopes []string `json:"scopes,omitempty"` + // Optional extra parameters for the requests to the token URL. + ExtraParams map[string]string `json:"extraParams,omitempty"` + // Caches and reuses the token until expired. + // Set it to false to force fetch the token at every authorization request regardless of expiration. + // +kubebuilder:default:=true + Cache *bool `json:"cache,omitempty"` +} + +// Settings of the OpendID Connect UserInfo linked to an OIDC-enabled JWT authentication config of this same AuthConfig. +type UserInfoMetadataSpec struct { + // The name of an OIDC-enabled JWT authentication config whose OpenID Connect configuration discovered includes the OIDC "userinfo_endpoint" claim. + IdentitySource string `json:"identitySource"` +} + +// Settings of the User-Managed Access (UMA) source of resource data. +type UmaMetadataSpec struct { + // 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. + Endpoint string `json:"endpoint"` + + // Reference to a Kubernetes secret in the same namespace, that stores client credentials to the resource registration API of the UMA server. + Credentials *k8score.LocalObjectReference `json:"credentialsRef"` +} + +type AuthorizationSpec struct { + CommonEvaluatorSpec `json:""` + AuthorizationMethodSpec `json:""` +} + +func (s *AuthorizationSpec) GetMethod() AuthorizationMethod { + if s.PatternMatching != nil { + return PatternMatchingAuthorization + } else if s.Opa != nil { + return OpaAuthorization + } else if s.KubernetesSubjectAccessReview != nil { + return KubernetesSubjectAccessReviewAuthorization + } else if s.SpiceDB != nil { + return SpiceDBAuthorization + } + return UnknownAuthorizationMethod +} + +type AuthorizationMethodSpec struct { + // Pattern-matching authorization rules. + PatternMatching *PatternMatchingAuthorizationSpec `json:"patternMatching,omitempty"` + // Open Policy Agent (OPA) Rego policy. + Opa *OpaAuthorizationSpec `json:"opa,omitempty"` + // Authorization by Kubernetes SubjectAccessReview + KubernetesSubjectAccessReview *KubernetesSubjectAccessReviewAuthorizationSpec `json:"kubernetesSubjectAccessReview,omitempty"` + // Authorization decision delegated to external Authzed/SpiceDB server. + SpiceDB *SpiceDBAuthorizationSpec `json:"spicedb,omitempty"` +} + +type PatternMatchingAuthorizationSpec struct { + Patterns []PatternExpressionOrRef `json:"patterns"` +} + +// Settings of the Open Policy Agent (OPA) authorization. +type OpaAuthorizationSpec struct { + // 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. + Rego string `json:"rego,omitempty"` + + // External registry of OPA policies. + External *ExternalOpaPolicy `json:"externalPolicy,omitempty"` + + // 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. + // +kubebuilder:default:=false + AllValues bool `json:"allValues,omitempty"` +} + +// ExternalOpaPolicy sets the configs for fetching OPA policies from an external source. +type ExternalOpaPolicy struct { + *HttpEndpointSpec `json:""` + + // Duration (in seconds) of the external data in the cache before pulled again from the source. + TTL int `json:"ttl,omitempty"` +} + +// Parameters of the Kubernetes SubjectAccessReview request. +type KubernetesSubjectAccessReviewAuthorizationSpec struct { + // User to check for authorization in the Kubernetes RBAC. + // Omit it to check for group authorization only. + User *ValueOrSelector `json:"user,omitempty"` + + // Groups the user must be a member of or, if `user` is omitted, the groups to check for authorization in the Kubernetes RBAC. + Groups []string `json:"groups,omitempty"` + + // Use resourceAttributes to check permissions on Kubernetes resources. + // If omitted, it performs a non-resource SubjectAccessReview, with verb and path inferred from the request. + // +optional + ResourceAttributes *KubernetesSubjectAccessReviewResourceAttributesSpec `json:"resourceAttributes,omitempty"` +} + +type KubernetesSubjectAccessReviewResourceAttributesSpec struct { + // API group of the resource. + // Use '*' for all API groups. + Group ValueOrSelector `json:"group,omitempty"` + // Resource kind + // Use '*' for all resource kinds. + Resource ValueOrSelector `json:"resource,omitempty"` + // Subresource kind + SubResource ValueOrSelector `json:"subresource,omitempty"` + // Resource name + // Omit it to check for authorization on all resources of the specified kind. + Name ValueOrSelector `json:"name,omitempty"` + // Namespace where the user must have permissions on the resource. + Namespace ValueOrSelector `json:"namespace,omitempty"` + // Verb to check for authorization on the resource. + // Use '*' for all verbs. + Verb ValueOrSelector `json:"verb,omitempty"` +} + +// Settings of the check request to the external SpiceDB server. +type SpiceDBAuthorizationSpec struct { + // Hostname and port number to the GRPC interface of the SpiceDB server (e.g. spicedb:50051). + Endpoint string `json:"endpoint"` + + // Insecure HTTP connection (i.e. disables TLS verification) + Insecure bool `json:"insecure,omitempty"` + + // Reference to a Secret key whose value will be used by Authorino to authenticate with the Authzed service. + SharedSecret *SecretKeyReference `json:"sharedSecretRef,omitempty"` + + // The subject that will be checked for the permission or relation. + Subject *SpiceDBObject `json:"subject,omitempty"` + + // The resource on which to check the permission or relation. + Resource *SpiceDBObject `json:"resource,omitempty"` + + // The name of the permission (or relation) on which to execute the check. + Permission ValueOrSelector `json:"permission,omitempty"` +} + +type SpiceDBObject struct { + Name ValueOrSelector `json:"name,omitempty"` + Kind ValueOrSelector `json:"kind,omitempty"` +} + +// Settings of the custom auth response. +type ResponseSpec struct { + // Customizations on the denial status attributes when the request is unauthenticated. + // For integration of Authorino via proxy, the proxy must honour the response status attributes specified in this config. + // Default: 401 Unauthorized + // +optional + Unauthenticated *DenyWithSpec `json:"unauthenticated,omitempty"` + + // Customizations on the denial status attributes when the request is unauthorized. + // For integration of Authorino via proxy, the proxy must honour the response status attributes specified in this config. + // Default: 403 Forbidden + // +optional + Unauthorized *DenyWithSpec `json:"unauthorized,omitempty"` + + // 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. + // +optional + Success WrappedSuccessResponseSpec `json:"success,omitempty"` +} + +// +kubebuilder:validation:Minimum:=300 +// +kubebuilder:validation:Maximum:=599 +type DenyWithCode int64 + +// Setting of the custom denial response. +type DenyWithSpec struct { + // HTTP status code to override the default denial status code. + Code DenyWithCode `json:"code,omitempty"` + + // HTTP message to override the default denial message. + Message *ValueOrSelector `json:"message,omitempty"` + + // HTTP response headers to override the default denial headers. + Headers NamedValuesOrSelectors `json:"headers,omitempty"` + + // HTTP response body to override the default denial body. + Body *ValueOrSelector `json:"body,omitempty"` +} + +// Settings of the custom success response. +type WrappedSuccessResponseSpec struct { + // Custom success response items wrapped as HTTP headers. + // For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + Headers map[string]HeaderSuccessResponseSpec `json:"headers,omitempty"` + + // Custom success response items wrapped as HTTP headers. + // For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. + // See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + DynamicMetadata map[string]SuccessResponseSpec `json:"dynamicMetadata,omitempty"` +} + +type HeaderSuccessResponseSpec struct { + // Prefix of the value to be added to the HTTP header + Prefix string `json:"prefix,omitempty"` + + SuccessResponseSpec `json:",omitempty"` +} + +// Settings of the success custom response item. +type SuccessResponseSpec struct { + CommonEvaluatorSpec `json:""` + AuthResponseMethodSpec `json:""` +} + +func (s *SuccessResponseSpec) GetMethod() AuthResponseMethod { + if s.Plain != nil { + return PlainAuthResponse + } else if s.Json != nil { + return JsonAuthResponse + } else if s.Wristband != nil { + return WristbandAuthResponse + } + return UnknownAuthResponseMethod +} + +// Settings of the custom success response item. +type AuthResponseMethodSpec struct { + // Plain text content + Plain *PlainAuthResponseSpec `json:"plain,omitempty"` + // 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. + Json *JsonAuthResponseSpec `json:"json,omitempty"` + // Authorino Festival Wristband token + Wristband *WristbandAuthResponseSpec `json:"wristband,omitempty"` +} + +// Static value or selector to set the plain custom response item. +type PlainAuthResponseSpec ValueOrSelector + +// List of properties of the JSON object to set the custom response item +// The values can be static or selected from the authorization JSON. +type JsonAuthResponseSpec struct { + Properties NamedValuesOrSelectors `json:"properties"` +} + +// Settings of the Festival Wristband token custom response item. +type WristbandAuthResponseSpec struct { + // The endpoint to the Authorino service that issues the wristband (format: ://:/, where = /://:/, + where = /://:/, + where = /://:/, + where = /://:/, + where = / Date: Tue, 25 Jul 2023 18:42:06 +0200 Subject: [PATCH 02/35] conversion webhook --- Makefile | 30 +- api/v1beta1/auth_config_conversion.go | 4 + api/v1beta2/auth_config_conversion.go | 1026 +++++++++++++++++ api/v1beta2/auth_config_webhook.go | 11 + deploy/authorino.yaml | 4 +- deploy/certs.yaml | 66 +- install/crd/kustomization.yaml | 10 +- install/crd/kustomizeconfig.yaml | 17 - .../patches/cainjection_in_authconfigs.yaml | 8 - .../crd/patches/webhook_in_authconfigs.yaml | 28 +- install/kustomization.yaml | 6 +- install/manifests.yaml | 3 +- install/webhooks/certificate.yaml | 21 + install/webhooks/deployment.yaml | 38 + install/webhooks/kustomization.yaml | 104 ++ .../patches/webhook_in_authconfigs.yaml | 19 + install/webhooks/rbac.yaml | 58 + install/webhooks/service.yaml | 11 + main.go | 442 ++++--- pkg/utils/utils.go | 9 + 20 files changed, 1665 insertions(+), 250 deletions(-) create mode 100644 api/v1beta1/auth_config_conversion.go create mode 100644 api/v1beta2/auth_config_conversion.go create mode 100644 api/v1beta2/auth_config_webhook.go delete mode 100644 install/crd/kustomizeconfig.yaml delete mode 100644 install/crd/patches/cainjection_in_authconfigs.yaml create mode 100644 install/webhooks/certificate.yaml create mode 100644 install/webhooks/deployment.yaml create mode 100644 install/webhooks/kustomization.yaml create mode 100644 install/webhooks/patches/webhook_in_authconfigs.yaml create mode 100644 install/webhooks/rbac.yaml create mode 100644 install/webhooks/service.yaml diff --git a/Makefile b/Makefile index 17205753..2288bc98 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ endif PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) export PATH := $(PROJECT_DIR)/bin:$(PATH) -# Authorino manifests bundle (CRDs, RBAC) +# Authorino manifests bundle (CRDs, RBAC, Webhook service) AUTHORINO_MANIFESTS ?= $(PROJECT_DIR)/install/manifests.yaml # The Kubernetes namespace where to deploy the Authorino instance @@ -109,7 +109,7 @@ generate: vendor controller-gen ## Generates types deepcopy code $(MAKE) fmt vet manifests: controller-gen kustomize ## Generates the manifests in $PROJECT_DIR/install - controller-gen crd:crdVersions=v1 rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=install/crd output:rbac:artifacts:config=install/rbac && kustomize build install > $(AUTHORINO_MANIFESTS) + controller-gen crd:crdVersions=v1 rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=install/crd output:rbac:artifacts:config=install/rbac && $(KUSTOMIZE) build install > $(AUTHORINO_MANIFESTS) run: generate manifests ## Runs the application against the Kubernetes cluster configured in ~/.kube/config go run -ldflags "-X main.version=$(VERSION)" ./main.go server @@ -188,7 +188,9 @@ limitador: ## Deploys Limitador from kuadrant/authorino-examples into the Kubern ##@ Installation -.PHONY: install-operator uninstall-operator install uninstall +.PHONY: install-operator uninstall-operator install-webhooks uninstall-webhooks install uninstall + +AUTHORINO_OPERATOR_NAMESPACE ?= authorino-operator ifeq (latest,$(OPERATOR_VERSION)) OPERATOR_BRANCH = main @@ -197,15 +199,29 @@ OPERATOR_BRANCH = $(OPERATOR_VERSION) endif install-operator: ## Installs Authorino Operator and corresponding version of the manifests into the Kubernetes cluster configured in ~/.kube/config kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/$(OPERATOR_BRANCH)/config/deploy/manifests.yaml - kubectl -n authorino-operator wait --timeout=300s --for=condition=Available deployments --all + kubectl -n $(AUTHORINO_OPERATOR_NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all uninstall-operator: ## Uninstalls Authorino Operator and corresponding version of the manifests from the Kubernetes cluster configured in ~/.kube/config kubectl delete -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/$(OPERATOR_BRANCH)/config/deploy/manifests.yaml -install: manifests ## Installs the current manifests (CRD, RBAC) into the Kubernetes cluster configured in ~/.kube/config +WEBHOOK_SERVICE_NAMESPACE ?= $(AUTHORINO_OPERATOR_NAMESPACE) + +install-webhooks: ## Creates the Authorino webhook service + cd install/webhooks && $(KUSTOMIZE) edit set namespace ${WEBHOOK_SERVICE_NAMESPACE} + $(KUSTOMIZE) build install/webhooks | kubectl -n $(WEBHOOK_SERVICE_NAMESPACE) apply -f - + # rollback kustomize edit + cd install/webhooks && $(KUSTOMIZE) edit set namespace authorino-webhooks + +uninstall-webhooks: ## Uninstalls the Authorino webhook service + cd install/webhooks && $(KUSTOMIZE) edit set namespace ${WEBHOOK_SERVICE_NAMESPACE} + $(KUSTOMIZE) build install/webhooks | kubectl -n $(WEBHOOK_SERVICE_NAMESPACE) delete -f - + # rollback kustomize edit + cd install/webhooks && $(KUSTOMIZE) edit set namespace authorino-webhooks + +install: manifests ## Installs the current manifests (CRD, RBAC, Webhook service) into the Kubernetes cluster configured in ~/.kube/config kubectl apply -f $(AUTHORINO_MANIFESTS) -uninstall: manifests ## Uninstalls the current manifests (CRD, RBAC) from the Kubernetes cluster configured in ~/.kube/config +uninstall: manifests ## Uninstalls the current manifests (CRD, RBAC, Webhook service) from the Kubernetes cluster configured in ~/.kube/config kubectl delete -f $(AUTHORINO_MANIFESTS) ##@ Deployment @@ -250,7 +266,7 @@ cluster: kind ## Starts a local Kubernetes cluster using Kind local-build: kind docker-build ## Builds an image based on the current branch and pushes it to the registry into the local Kubernetes cluster started with Kind $(KIND) load docker-image $(AUTHORINO_IMAGE) --name $(KIND_CLUSTER_NAME) -local-setup: cluster local-build cert-manager install-operator install namespace deploy user-apps ## Sets up a test/dev local Kubernetes server using Kind, loaded up with a freshly built Authorino image and apps +local-setup: cluster local-build cert-manager install-operator install install-webhooks namespace deploy user-apps ## Sets up a test/dev local Kubernetes server using Kind, loaded up with a freshly built Authorino image and apps kubectl -n $(NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all @{ \ echo "Now you can export the envoy service by doing:"; \ diff --git a/api/v1beta1/auth_config_conversion.go b/api/v1beta1/auth_config_conversion.go new file mode 100644 index 00000000..6b810a0f --- /dev/null +++ b/api/v1beta1/auth_config_conversion.go @@ -0,0 +1,4 @@ +package v1beta1 + +// Hub marks this version as a conversion hub. +func (a *AuthConfig) Hub() {} diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go new file mode 100644 index 00000000..0a411efb --- /dev/null +++ b/api/v1beta2/auth_config_conversion.go @@ -0,0 +1,1026 @@ +package v1beta2 + +import ( + "github.com/kuadrant/authorino/api/v1beta1" + "github.com/kuadrant/authorino/pkg/utils" + + k8sruntime "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +func (src *AuthConfig) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*v1beta1.AuthConfig) + + logger := ctrl.Log.WithName("webhook").WithName("authconfig").WithName("converto").WithValues("src", src) + logger.V(1).Info("starting converting resource") + + // metadata + dst.ObjectMeta = src.ObjectMeta + + // hosts + dst.Spec.Hosts = src.Spec.Hosts + + // named patterns + if src.Spec.NamedPatterns != nil { + dst.Spec.Patterns = make(map[string]v1beta1.JSONPatternExpressions, len(src.Spec.NamedPatterns)) + for name, patterns := range src.Spec.NamedPatterns { + dst.Spec.Patterns[name] = utils.Map(patterns, convertPatternExpressionTo) + } + } + + // conditions + dst.Spec.Conditions = utils.Map(src.Spec.Conditions, convertPatternExpressionOrRefTo) + + // identity + for name, authentication := range src.Spec.Authentication { + identity := convertAuthenticationTo(name, authentication) + dst.Spec.Identity = append(dst.Spec.Identity, identity) + } + + // metadata + for name, metadataSrc := range src.Spec.Metadata { + metadata := convertMetadataTo(name, metadataSrc) + dst.Spec.Metadata = append(dst.Spec.Metadata, metadata) + } + + // authorization + for name, authorizationSrc := range src.Spec.Authorization { + authorization := convertAuthorizationTo(name, authorizationSrc) + dst.Spec.Authorization = append(dst.Spec.Authorization, authorization) + } + + // response + if src.Spec.Response != nil { + for name, responseSrc := range src.Spec.Response.Success.Headers { + response := convertSuccessResponseTo(name, responseSrc.SuccessResponseSpec, "httpHeader") + dst.Spec.Response = append(dst.Spec.Response, response) + } + + for name, responseSrc := range src.Spec.Response.Success.DynamicMetadata { + response := convertSuccessResponseTo(name, responseSrc, "envoyDynamicMetadata") + dst.Spec.Response = append(dst.Spec.Response, response) + } + + // denyWith + if src.Spec.Response.Unauthenticated != nil || src.Spec.Response.Unauthorized != nil { + dst.Spec.DenyWith = &v1beta1.DenyWith{} + } + + if denyWithSrc := src.Spec.Response.Unauthenticated; denyWithSrc != nil { + dst.Spec.DenyWith.Unauthenticated = convertDenyWithSpecTo(denyWithSrc) + } + + if denyWithSrc := src.Spec.Response.Unauthorized; denyWithSrc != nil { + dst.Spec.DenyWith.Unauthorized = convertDenyWithSpecTo(denyWithSrc) + } + } + + // callbacks + for name, callbackSrc := range src.Spec.Callbacks { + callback := convertCallbackTo(name, callbackSrc) + dst.Spec.Callbacks = append(dst.Spec.Callbacks, callback) + } + + // status + dst.Status = convertStatusTo(src.Status) + + logger.V(1).Info("finished converting resource", "dst", dst) + + return nil +} + +func (dst *AuthConfig) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*v1beta1.AuthConfig) + + logger := ctrl.Log.WithName("webhook").WithName("authconfig").WithName("converfrom").WithValues("src", src) + logger.V(1).Info("starting converting resource") + + // metadata + dst.ObjectMeta = src.ObjectMeta + + // hosts + dst.Spec.Hosts = src.Spec.Hosts + + // named patterns + if src.Spec.Patterns != nil { + dst.Spec.NamedPatterns = make(map[string]PatternExpressions, len(src.Spec.Patterns)) + for name, patterns := range src.Spec.Patterns { + dst.Spec.NamedPatterns[name] = utils.Map(patterns, convertPatternExpressionFrom) + } + } + + // conditions + dst.Spec.Conditions = utils.Map(src.Spec.Conditions, convertPatternExpressionOrRefFrom) + + // authentication + if src.Spec.Identity != nil { + dst.Spec.Authentication = make(map[string]AuthenticationSpec, len(src.Spec.Identity)) + for _, identity := range src.Spec.Identity { + name, authentication := convertAuthenticationFrom(identity) + dst.Spec.Authentication[name] = authentication + } + } + + // metadata + if src.Spec.Metadata != nil { + dst.Spec.Metadata = make(map[string]MetadataSpec, len(src.Spec.Metadata)) + for _, metadataSrc := range src.Spec.Metadata { + name, metadata := convertMetadataFrom(metadataSrc) + dst.Spec.Metadata[name] = metadata + } + } + + // authorization + if src.Spec.Authorization != nil { + dst.Spec.Authorization = make(map[string]AuthorizationSpec, len(src.Spec.Authorization)) + for _, authorizationSrc := range src.Spec.Authorization { + name, authorization := convertAuthorizationFrom(authorizationSrc) + dst.Spec.Authorization[name] = authorization + } + } + + // response + denyWith := src.Spec.DenyWith + + if denyWith != nil || len(src.Spec.Response) > 0 { + dst.Spec.Response = &ResponseSpec{} + } + + if denyWith != nil && denyWith.Unauthenticated != nil { + dst.Spec.Response.Unauthenticated = convertDenyWithSpecFrom(denyWith.Unauthenticated) + } + + if denyWith != nil && denyWith.Unauthorized != nil { + dst.Spec.Response.Unauthorized = convertDenyWithSpecFrom(denyWith.Unauthorized) + } + + for _, responseSrc := range src.Spec.Response { + if responseSrc.Wrapper != "httpHeader" { + continue + } + if dst.Spec.Response.Success.Headers == nil { + dst.Spec.Response.Success.Headers = make(map[string]HeaderSuccessResponseSpec) + } + name, response := convertSuccessResponseFrom(responseSrc) + dst.Spec.Response.Success.Headers[name] = HeaderSuccessResponseSpec{ + SuccessResponseSpec: response, + } + } + + for _, responseSrc := range src.Spec.Response { + if responseSrc.Wrapper != "envoyDynamicMetadata" { + continue + } + if dst.Spec.Response.Success.DynamicMetadata == nil { + dst.Spec.Response.Success.DynamicMetadata = make(map[string]SuccessResponseSpec) + } + name, response := convertSuccessResponseFrom(responseSrc) + dst.Spec.Response.Success.DynamicMetadata[name] = response + } + + // callbacks + if src.Spec.Callbacks != nil { + dst.Spec.Callbacks = make(map[string]CallbackSpec, len(src.Spec.Callbacks)) + for _, callbackSrc := range src.Spec.Callbacks { + name, callback := convertCallbackFrom(callbackSrc) + dst.Spec.Callbacks[name] = callback + } + } + + // status + dst.Status = convertStatusFrom(src.Status) + + logger.V(1).Info("finished converting resource", "dst", dst) + + return nil +} + +func convertPatternExpressionTo(src PatternExpression) v1beta1.JSONPatternExpression { + return v1beta1.JSONPatternExpression{ + Selector: src.Selector, + Operator: v1beta1.JSONPatternOperator(src.Operator), + Value: src.Value, + } +} + +func convertPatternExpressionFrom(src v1beta1.JSONPatternExpression) PatternExpression { + return PatternExpression{ + Selector: src.Selector, + Operator: PatternExpressionOperator(src.Operator), + Value: src.Value, + } +} + +func convertPatternExpressionOrRefTo(src PatternExpressionOrRef) v1beta1.JSONPattern { + return v1beta1.JSONPattern{ + JSONPatternExpression: convertPatternExpressionTo(src.PatternExpression), + JSONPatternRef: v1beta1.JSONPatternRef{ + JSONPatternName: src.PatternRef.Name, + }, + } +} + +func convertPatternExpressionOrRefFrom(src v1beta1.JSONPattern) PatternExpressionOrRef { + return PatternExpressionOrRef{ + PatternExpression: convertPatternExpressionFrom(src.JSONPatternExpression), + PatternRef: PatternRef{ + Name: src.JSONPatternRef.JSONPatternName, + }, + } +} + +func convertEvaluatorCachingTo(src *EvaluatorCaching) *v1beta1.EvaluatorCaching { + if src == nil { + return nil + } + return &v1beta1.EvaluatorCaching{ + Key: convertValueOrSelectorTo(src.Key), + TTL: src.TTL, + } +} + +func convertEvaluatorCachingFrom(src *v1beta1.EvaluatorCaching) *EvaluatorCaching { + if src == nil { + return nil + } + return &EvaluatorCaching{ + Key: convertValueOrSelectorFrom(src.Key), + TTL: src.TTL, + } +} + +func convertValueOrSelectorTo(src ValueOrSelector) v1beta1.StaticOrDynamicValue { + return v1beta1.StaticOrDynamicValue{ + Value: string(src.Value.Raw), + ValueFrom: convertSelectorTo(src), + } +} + +func convertValueOrSelectorFrom(src v1beta1.StaticOrDynamicValue) ValueOrSelector { + value := k8sruntime.RawExtension{} + if src.ValueFrom.AuthJSON == "" { + value.Raw = []byte(src.Value) + } + return ValueOrSelector{ + Value: value, + Selector: src.ValueFrom.AuthJSON, + } +} + +func convertPtrValueOrSelectorTo(src *ValueOrSelector) *v1beta1.StaticOrDynamicValue { + if src == nil { + return nil + } + v := convertValueOrSelectorTo(*src) + return &v +} + +func convertPtrValueOrSelectorFrom(src *v1beta1.StaticOrDynamicValue) *ValueOrSelector { + if src == nil { + return nil + } + v := convertValueOrSelectorFrom(*src) + return &v +} + +func convertNamedValuesOrSelectorsTo(src NamedValuesOrSelectors) (jsonProperties []v1beta1.JsonProperty) { + for name, valueOrSelector := range src { + jsonProperties = append(jsonProperties, v1beta1.JsonProperty{ + Name: name, + Value: valueOrSelector.Value, + ValueFrom: convertSelectorTo(valueOrSelector), + }) + } + return +} + +func convertNamedValuesOrSelectorsFrom(src []v1beta1.JsonProperty) NamedValuesOrSelectors { + namedValuesOrSelectors := NamedValuesOrSelectors{} + for _, jsonProperty := range src { + namedValuesOrSelectors[jsonProperty.Name] = ValueOrSelector{ + Value: jsonProperty.Value, + Selector: jsonProperty.ValueFrom.AuthJSON, + } + } + return namedValuesOrSelectors +} + +func convertSelectorTo(src ValueOrSelector) v1beta1.ValueFrom { + return v1beta1.ValueFrom{ + AuthJSON: src.Selector, + } +} + +func convertCredentialsTo(src Credentials) v1beta1.Credentials { + var in, key string + switch src.GetType() { + case AuthorizationHeaderCredentials: + in = "authorization_header" + key = src.AuthorizationHeader.Prefix + case CustomHeaderCredentials: + in = "custom_header" + key = src.CustomHeader.Name + case QueryStringCredentials: + in = "query" + key = src.QueryString.Name + case CookieCredentials: + in = "cookie" + key = src.Cookie.Name + } + return v1beta1.Credentials{ + In: v1beta1.Credentials_In(in), + KeySelector: key, + } +} + +func convertCredentialsFrom(src v1beta1.Credentials) Credentials { + credentials := Credentials{} + switch src.In { + case "authorization_header": + credentials.AuthorizationHeader = &Prefixed{ + Prefix: src.KeySelector, + } + case "custom_header": + credentials.CustomHeader = &CustomHeader{ + Named: Named{Name: src.KeySelector}, + } + case "query": + credentials.QueryString = &Named{ + Name: src.KeySelector, + } + case "cookie": + credentials.Cookie = &Named{ + Name: src.KeySelector, + } + } + return credentials +} + +func convertAuthenticationTo(name string, src AuthenticationSpec) *v1beta1.Identity { + extendedProperties := utils.Map(convertNamedValuesOrSelectorsTo(NamedValuesOrSelectors(src.Overrides)), func(jsonProperty v1beta1.JsonProperty) v1beta1.ExtendedProperty { + return v1beta1.ExtendedProperty{ + JsonProperty: jsonProperty, + Overwrite: true, + } + }) + extendedProperties = append(extendedProperties, utils.Map(convertNamedValuesOrSelectorsTo(NamedValuesOrSelectors(src.Defaults)), func(jsonProperty v1beta1.JsonProperty) v1beta1.ExtendedProperty { + return v1beta1.ExtendedProperty{ + JsonProperty: jsonProperty, + Overwrite: false, + } + })...) + + identity := &v1beta1.Identity{ + Name: name, + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefTo), + Cache: convertEvaluatorCachingTo(src.Cache), + Credentials: convertCredentialsTo(src.Credentials), + ExtendedProperties: extendedProperties, + } + + switch src.GetMethod() { + case ApiKeyAuthentication: + selector := *src.ApiKey.Selector + identity.APIKey = &v1beta1.Identity_APIKey{ + Selector: &selector, + AllNamespaces: src.ApiKey.AllNamespaces, + } + case JwtAuthentication: + identity.Oidc = &v1beta1.Identity_OidcConfig{ + Endpoint: src.Jwt.IssuerUrl, + TTL: src.Jwt.TTL, + } + case OAuth2TokenIntrospectionAuthentication: + credentials := *src.OAuth2TokenIntrospection.Credentials + identity.OAuth2 = &v1beta1.Identity_OAuth2Config{ + TokenIntrospectionUrl: src.OAuth2TokenIntrospection.Url, + TokenTypeHint: src.OAuth2TokenIntrospection.TokenTypeHint, + Credentials: &credentials, + } + case KubernetesTokenReviewAuthentication: + identity.KubernetesAuth = &v1beta1.Identity_KubernetesAuth{ + Audiences: src.KubernetesTokenReview.Audiences, + } + case X509ClientCertificateAuthentication: + selector := *src.X509ClientCertificate.Selector + identity.MTLS = &v1beta1.Identity_MTLS{ + Selector: &selector, + AllNamespaces: src.X509ClientCertificate.AllNamespaces, + } + case PlainIdentityAuthentication: + selector := v1beta1.Identity_Plain(v1beta1.ValueFrom{ + AuthJSON: src.Plain.Selector, + }) + identity.Plain = &selector + case AnonymousAccessAuthentication: + identity.Anonymous = &v1beta1.Identity_Anonymous{} + } + + return identity +} + +func convertAuthenticationFrom(src *v1beta1.Identity) (string, AuthenticationSpec) { + var overrides []v1beta1.JsonProperty + for _, extendedProperty := range src.ExtendedProperties { + if !extendedProperty.Overwrite { + continue + } + overrides = append(overrides, extendedProperty.JsonProperty) + } + + var defaults []v1beta1.JsonProperty + for _, extendedProperty := range src.ExtendedProperties { + if extendedProperty.Overwrite { + continue + } + defaults = append(defaults, extendedProperty.JsonProperty) + } + + authentication := AuthenticationSpec{ + CommonEvaluatorSpec: CommonEvaluatorSpec{ + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), + Cache: convertEvaluatorCachingFrom(src.Cache), + }, + Credentials: convertCredentialsFrom(src.Credentials), + Overrides: ExtendedProperties(convertNamedValuesOrSelectorsFrom(overrides)), + Defaults: ExtendedProperties(convertNamedValuesOrSelectorsFrom(defaults)), + } + + switch src.GetType() { + case v1beta1.IdentityApiKey: + selector := *src.APIKey.Selector + authentication.ApiKey = &ApiKeyAuthenticationSpec{ + Selector: &selector, + AllNamespaces: src.APIKey.AllNamespaces, + } + case v1beta1.IdentityOidc: + authentication.Jwt = &JwtAuthenticationSpec{ + IssuerUrl: src.Oidc.Endpoint, + TTL: src.Oidc.TTL, + } + case v1beta1.IdentityOAuth2: + credentials := *src.OAuth2.Credentials + authentication.OAuth2TokenIntrospection = &OAuth2TokenIntrospectionSpec{ + Url: src.OAuth2.TokenIntrospectionUrl, + TokenTypeHint: src.OAuth2.TokenTypeHint, + Credentials: &credentials, + } + case v1beta1.IdentityKubernetesAuth: + authentication.KubernetesTokenReview = &KubernetesTokenReviewSpec{ + Audiences: src.KubernetesAuth.Audiences, + } + case v1beta1.IdentityMTLS: + selector := *src.MTLS.Selector + authentication.X509ClientCertificate = &X509ClientCertificateAuthenticationSpec{ + Selector: &selector, + AllNamespaces: src.MTLS.AllNamespaces, + } + case v1beta1.IdentityPlain: + authentication.Plain = &PlainIdentitySpec{ + Selector: src.Plain.AuthJSON, + } + case v1beta1.IdentityAnonymous: + authentication.AnonymousAccess = &AnonymousAccessSpec{} + } + + return src.Name, authentication +} + +func convertMetadataTo(name string, src MetadataSpec) *v1beta1.Metadata { + metadata := &v1beta1.Metadata{ + Name: name, + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefTo), + Cache: convertEvaluatorCachingTo(src.Cache), + } + + switch src.GetMethod() { + case HttpMetadata: + metadata.GenericHTTP = convertHttpEndpointSpecTo(src.Http) + case UserInfoMetadata: + metadata.UserInfo = &v1beta1.Metadata_UserInfo{ + IdentitySource: src.UserInfo.IdentitySource, + } + case UmaResourceMetadata: + credentials := *src.Uma.Credentials + metadata.UMA = &v1beta1.Metadata_UMA{ + Endpoint: src.Uma.Endpoint, + Credentials: &credentials, + } + } + + return metadata +} + +func convertMetadataFrom(src *v1beta1.Metadata) (string, MetadataSpec) { + metadata := MetadataSpec{ + CommonEvaluatorSpec: CommonEvaluatorSpec{ + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), + Cache: convertEvaluatorCachingFrom(src.Cache), + }, + } + + switch src.GetType() { + case v1beta1.MetadataGenericHTTP: + metadata.Http = convertHttpEndpointSpecFrom(src.GenericHTTP) + case v1beta1.MetadataUserinfo: + metadata.UserInfo = &UserInfoMetadataSpec{ + IdentitySource: src.UserInfo.IdentitySource, + } + case v1beta1.MetadataUma: + credentials := *src.UMA.Credentials + metadata.Uma = &UmaMetadataSpec{ + Endpoint: src.UMA.Endpoint, + Credentials: &credentials, + } + } + + return src.Name, metadata +} + +func convertHttpEndpointSpecTo(src *HttpEndpointSpec) *v1beta1.Metadata_GenericHTTP { + if src == nil { + return nil + } + return &v1beta1.Metadata_GenericHTTP{ + Endpoint: src.Url, + Method: convertMethodTo(src.Method), + Body: convertPtrValueOrSelectorTo(src.Body), + Parameters: convertNamedValuesOrSelectorsTo(src.Parameters), + ContentType: convertContentTypeTo(src.ContentType), + Headers: convertNamedValuesOrSelectorsTo(src.Headers), + SharedSecret: convertSecretKeyReferenceTo(src.SharedSecret), + OAuth2: convertOAuth2ClientAuthenticationTo(src.OAuth2), + Credentials: convertCredentialsTo(src.Credentials), + } +} + +func convertHttpEndpointSpecFrom(src *v1beta1.Metadata_GenericHTTP) *HttpEndpointSpec { + if src == nil { + return nil + } + return &HttpEndpointSpec{ + Url: src.Endpoint, + Method: convertMethodFrom(src.Method), + Body: convertPtrValueOrSelectorFrom(src.Body), + Parameters: convertNamedValuesOrSelectorsFrom(src.Parameters), + ContentType: convertContentTypeFrom(src.ContentType), + Headers: convertNamedValuesOrSelectorsFrom(src.Headers), + SharedSecret: convertSecretKeyReferenceFrom(src.SharedSecret), + OAuth2: convertOAuth2ClientAuthenticationFrom(src.OAuth2), + Credentials: convertCredentialsFrom(src.Credentials), + } +} + +func convertMethodTo(src *HttpMethod) *v1beta1.GenericHTTP_Method { + if src == nil { + return nil + } + method := v1beta1.GenericHTTP_Method(*src) + return &method +} + +func convertMethodFrom(src *v1beta1.GenericHTTP_Method) *HttpMethod { + if src == nil { + return nil + } + method := HttpMethod(*src) + return &method +} + +func convertContentTypeTo(src HttpContentType) v1beta1.Metadata_GenericHTTP_ContentType { + return v1beta1.Metadata_GenericHTTP_ContentType(src) +} + +func convertContentTypeFrom(src v1beta1.Metadata_GenericHTTP_ContentType) HttpContentType { + return HttpContentType(src) +} + +func convertOAuth2ClientAuthenticationTo(src *OAuth2ClientAuthentication) *v1beta1.OAuth2ClientAuthentication { + if src == nil { + return nil + } + o := &v1beta1.OAuth2ClientAuthentication{ + TokenUrl: src.TokenUrl, + ClientId: src.ClientId, + ClientSecret: *convertSecretKeyReferenceTo(&src.ClientSecret), + Scopes: src.Scopes, + ExtraParams: src.ExtraParams, + } + if src.Cache != nil { + cache := *src.Cache + o.Cache = &cache + } + return o +} + +func convertOAuth2ClientAuthenticationFrom(src *v1beta1.OAuth2ClientAuthentication) *OAuth2ClientAuthentication { + if src == nil { + return nil + } + o := &OAuth2ClientAuthentication{ + TokenUrl: src.TokenUrl, + ClientId: src.ClientId, + ClientSecret: *convertSecretKeyReferenceFrom(&src.ClientSecret), + Scopes: src.Scopes, + ExtraParams: src.ExtraParams, + } + if src.Cache != nil { + cache := *src.Cache + o.Cache = &cache + } + return o +} + +func convertSecretKeyReferenceTo(src *SecretKeyReference) *v1beta1.SecretKeyReference { + if src == nil { + return nil + } + return &v1beta1.SecretKeyReference{ + Name: src.Name, + Key: src.Key, + } +} + +func convertSecretKeyReferenceFrom(src *v1beta1.SecretKeyReference) *SecretKeyReference { + if src == nil { + return nil + } + return &SecretKeyReference{ + Name: src.Name, + Key: src.Key, + } +} + +func convertAuthorizationTo(name string, src AuthorizationSpec) *v1beta1.Authorization { + authorization := &v1beta1.Authorization{ + Name: name, + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefTo), + Cache: convertEvaluatorCachingTo(src.Cache), + } + + switch src.GetMethod() { + case PatternMatchingAuthorization: + authorization.JSON = &v1beta1.Authorization_JSONPatternMatching{ + Rules: utils.Map(src.PatternMatching.Patterns, convertPatternExpressionOrRefTo), + } + case OpaAuthorization: + authorization.OPA = &v1beta1.Authorization_OPA{ + InlineRego: src.Opa.Rego, + ExternalRegistry: convertOpaExternalRegistryTo(src.Opa.External), + AllValues: src.Opa.AllValues, + } + case KubernetesSubjectAccessReviewAuthorization: + authorization.KubernetesAuthz = &v1beta1.Authorization_KubernetesAuthz{ + Groups: src.KubernetesSubjectAccessReview.Groups, + ResourceAttributes: convertKubernetesSubjectAccessReviewResourceAttributesTo(src.KubernetesSubjectAccessReview.ResourceAttributes), + } + if src.KubernetesSubjectAccessReview.User != nil { + authorization.KubernetesAuthz.User = convertValueOrSelectorTo(*src.KubernetesSubjectAccessReview.User) + } + case SpiceDBAuthorization: + authorization.Authzed = &v1beta1.Authorization_Authzed{ + Endpoint: src.SpiceDB.Endpoint, + Insecure: src.SpiceDB.Insecure, + SharedSecret: convertSecretKeyReferenceTo(src.SpiceDB.SharedSecret), + Subject: SpiceDBObjectTo(src.SpiceDB.Subject), + Resource: SpiceDBObjectTo(src.SpiceDB.Resource), + Permission: convertValueOrSelectorTo(src.SpiceDB.Permission), + } + } + + return authorization +} + +func convertAuthorizationFrom(src *v1beta1.Authorization) (string, AuthorizationSpec) { + authorization := AuthorizationSpec{ + CommonEvaluatorSpec: CommonEvaluatorSpec{ + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), + Cache: convertEvaluatorCachingFrom(src.Cache), + }, + } + + switch src.GetType() { + case v1beta1.AuthorizationJSONPatternMatching: + authorization.PatternMatching = &PatternMatchingAuthorizationSpec{ + Patterns: utils.Map(src.JSON.Rules, convertPatternExpressionOrRefFrom), + } + case v1beta1.AuthorizationOPA: + authorization.Opa = &OpaAuthorizationSpec{ + Rego: src.OPA.InlineRego, + External: convertOpaExternalRegistryFrom(src.OPA.ExternalRegistry), + AllValues: src.OPA.AllValues, + } + case v1beta1.AuthorizationKubernetesAuthz: + authorization.KubernetesSubjectAccessReview = &KubernetesSubjectAccessReviewAuthorizationSpec{ + User: convertPtrValueOrSelectorFrom(&src.KubernetesAuthz.User), + Groups: src.KubernetesAuthz.Groups, + ResourceAttributes: convertKubernetesSubjectAccessReviewResourceAttributesFrom(src.KubernetesAuthz.ResourceAttributes), + } + case v1beta1.AuthorizationAuthzed: + authorization.SpiceDB = &SpiceDBAuthorizationSpec{ + Endpoint: src.Authzed.Endpoint, + Insecure: src.Authzed.Insecure, + SharedSecret: convertSecretKeyReferenceFrom(src.Authzed.SharedSecret), + Subject: SpiceDBObjectFrom(src.Authzed.Subject), + Resource: SpiceDBObjectFrom(src.Authzed.Resource), + Permission: convertValueOrSelectorFrom(src.Authzed.Permission), + } + } + + return src.Name, authorization +} + +func convertOpaExternalRegistryTo(src *ExternalOpaPolicy) v1beta1.ExternalRegistry { + if src == nil { + return v1beta1.ExternalRegistry{} + } + return v1beta1.ExternalRegistry{ + Endpoint: src.Url, + SharedSecret: convertSecretKeyReferenceTo(src.SharedSecret), + Credentials: convertCredentialsTo(src.Credentials), + TTL: src.TTL, + } +} + +func convertOpaExternalRegistryFrom(src v1beta1.ExternalRegistry) *ExternalOpaPolicy { + if src.Endpoint == "" { + return nil + } + return &ExternalOpaPolicy{ + HttpEndpointSpec: &HttpEndpointSpec{ + Url: src.Endpoint, + SharedSecret: convertSecretKeyReferenceFrom(src.SharedSecret), + Credentials: convertCredentialsFrom(src.Credentials), + }, + TTL: src.TTL, + } +} + +func convertKubernetesSubjectAccessReviewResourceAttributesTo(src *KubernetesSubjectAccessReviewResourceAttributesSpec) *v1beta1.Authorization_KubernetesAuthz_ResourceAttributes { + if src == nil { + return nil + } + return &v1beta1.Authorization_KubernetesAuthz_ResourceAttributes{ + Namespace: convertValueOrSelectorTo(src.Namespace), + Group: convertValueOrSelectorTo(src.Group), + Resource: convertValueOrSelectorTo(src.Resource), + Name: convertValueOrSelectorTo(src.Name), + SubResource: convertValueOrSelectorTo(src.SubResource), + Verb: convertValueOrSelectorTo(src.Verb), + } +} + +func convertKubernetesSubjectAccessReviewResourceAttributesFrom(src *v1beta1.Authorization_KubernetesAuthz_ResourceAttributes) *KubernetesSubjectAccessReviewResourceAttributesSpec { + if src == nil { + return nil + } + return &KubernetesSubjectAccessReviewResourceAttributesSpec{ + Namespace: convertValueOrSelectorFrom(src.Namespace), + Group: convertValueOrSelectorFrom(src.Group), + Resource: convertValueOrSelectorFrom(src.Resource), + Name: convertValueOrSelectorFrom(src.Name), + SubResource: convertValueOrSelectorFrom(src.SubResource), + Verb: convertValueOrSelectorFrom(src.Verb), + } +} + +func SpiceDBObjectTo(src *SpiceDBObject) *v1beta1.AuthzedObject { + if src == nil { + return nil + } + return &v1beta1.AuthzedObject{ + Kind: convertValueOrSelectorTo(src.Kind), + Name: convertValueOrSelectorTo(src.Name), + } +} + +func SpiceDBObjectFrom(src *v1beta1.AuthzedObject) *SpiceDBObject { + if src == nil { + return nil + } + return &SpiceDBObject{ + Kind: convertValueOrSelectorFrom(src.Kind), + Name: convertValueOrSelectorFrom(src.Name), + } +} + +func convertSuccessResponseTo(name string, src SuccessResponseSpec, wrapper string) *v1beta1.Response { + response := &v1beta1.Response{ + Name: name, + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefTo), + Cache: convertEvaluatorCachingTo(src.Cache), + Wrapper: v1beta1.Response_Wrapper(wrapper), + WrapperKey: name, + } + + switch src.GetMethod() { + case PlainAuthResponse: + selector := v1beta1.Response_Plain(convertValueOrSelectorTo(ValueOrSelector(*src.Plain))) + response.Plain = &selector + case JsonAuthResponse: + response.JSON = &v1beta1.Response_DynamicJSON{ + Properties: convertNamedValuesOrSelectorsTo(src.Json.Properties), + } + case WristbandAuthResponse: + response.Wristband = &v1beta1.Response_Wristband{ + Issuer: src.Wristband.Issuer, + CustomClaims: convertNamedValuesOrSelectorsTo(src.Wristband.CustomClaims), + } + if src.Wristband.TokenDuration != nil { + duration := *src.Wristband.TokenDuration + response.Wristband.TokenDuration = &duration + } + for _, keySrc := range src.Wristband.SigningKeyRefs { + if keySrc == nil { + continue + } + key := v1beta1.SigningKeyRef{ + Name: keySrc.Name, + Algorithm: v1beta1.SigningKeyAlgorithm(keySrc.Algorithm), + } + response.Wristband.SigningKeyRefs = append(response.Wristband.SigningKeyRefs, &key) + } + } + + return response +} + +func convertSuccessResponseFrom(src *v1beta1.Response) (string, SuccessResponseSpec) { + response := SuccessResponseSpec{ + CommonEvaluatorSpec: CommonEvaluatorSpec{ + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), + Cache: convertEvaluatorCachingFrom(src.Cache), + }, + } + + switch src.GetType() { + case v1beta1.ResponsePlain: + selector := PlainAuthResponseSpec(convertValueOrSelectorFrom(v1beta1.StaticOrDynamicValue(*src.Plain))) + response.Plain = &selector + case v1beta1.ResponseDynamicJSON: + response.Json = &JsonAuthResponseSpec{ + Properties: convertNamedValuesOrSelectorsFrom(src.JSON.Properties), + } + case v1beta1.ResponseWristband: + response.Wristband = &WristbandAuthResponseSpec{ + Issuer: src.Wristband.Issuer, + CustomClaims: convertNamedValuesOrSelectorsFrom(src.Wristband.CustomClaims), + } + if src.Wristband.TokenDuration != nil { + duration := *src.Wristband.TokenDuration + response.Wristband.TokenDuration = &duration + } + for _, keySrc := range src.Wristband.SigningKeyRefs { + if keySrc == nil { + continue + } + key := &WristbandSigningKeyRef{ + Name: keySrc.Name, + Algorithm: WristbandSigningKeyAlgorithm(keySrc.Algorithm), + } + response.Wristband.SigningKeyRefs = append(response.Wristband.SigningKeyRefs, key) + } + } + + return src.Name, response +} + +func convertDenyWithSpecTo(src *DenyWithSpec) *v1beta1.DenyWithSpec { + if src == nil { + return nil + } + return &v1beta1.DenyWithSpec{ + Code: v1beta1.DenyWith_Code(src.Code), + Headers: convertNamedValuesOrSelectorsTo(src.Headers), + Message: convertPtrValueOrSelectorTo(src.Message), + Body: convertPtrValueOrSelectorTo(src.Body), + } +} + +func convertDenyWithSpecFrom(src *v1beta1.DenyWithSpec) *DenyWithSpec { + if src == nil { + return nil + } + return &DenyWithSpec{ + Code: DenyWithCode(src.Code), + Headers: convertNamedValuesOrSelectorsFrom(src.Headers), + Message: convertPtrValueOrSelectorFrom(src.Message), + Body: convertPtrValueOrSelectorFrom(src.Body), + } +} + +func convertCallbackTo(name string, src CallbackSpec) *v1beta1.Callback { + callback := &v1beta1.Callback{ + Name: name, + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefTo), + } + + switch src.GetMethod() { + case HttpCallback: + callback.HTTP = convertHttpEndpointSpecTo(src.Http) + } + + return callback +} + +func convertCallbackFrom(src *v1beta1.Callback) (string, CallbackSpec) { + callback := CallbackSpec{ + CommonEvaluatorSpec: CommonEvaluatorSpec{ + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), + }, + } + + switch src.GetType() { + case v1beta1.CallbackHTTP: + callback.Http = convertHttpEndpointSpecFrom(src.HTTP) + } + + return src.Name, callback +} + +func convertStatusTo(src AuthConfigStatus) v1beta1.AuthConfigStatus { + return v1beta1.AuthConfigStatus{ + Conditions: utils.Map(src.Conditions, func(conditionSrc AuthConfigStatusCondition) v1beta1.Condition { + condition := v1beta1.Condition{ + Type: v1beta1.ConditionType(conditionSrc.Type), + Status: conditionSrc.Status, + LastTransitionTime: conditionSrc.LastTransitionTime, + Reason: conditionSrc.Reason, + Message: conditionSrc.Message, + } + if conditionSrc.LastUpdatedTime != nil { + time := *conditionSrc.LastUpdatedTime + condition.LastUpdatedTime = &time + } + return condition + }), + Summary: convertStatusSummaryTo(src.Summary), + } +} + +func convertStatusFrom(src v1beta1.AuthConfigStatus) AuthConfigStatus { + return AuthConfigStatus{ + Conditions: utils.Map(src.Conditions, func(conditionSrc v1beta1.Condition) AuthConfigStatusCondition { + condition := AuthConfigStatusCondition{ + Type: StatusConditionType(conditionSrc.Type), + Status: conditionSrc.Status, + LastTransitionTime: conditionSrc.LastTransitionTime, + Reason: conditionSrc.Reason, + Message: conditionSrc.Message, + } + if conditionSrc.LastUpdatedTime != nil { + time := *conditionSrc.LastUpdatedTime + condition.LastUpdatedTime = &time + } + return condition + }), + Summary: convertStatusSummaryFrom(src.Summary), + } +} + +func convertStatusSummaryTo(src AuthConfigStatusSummary) v1beta1.Summary { + return v1beta1.Summary{ + Ready: src.Ready, + HostsReady: src.HostsReady, + NumHostsReady: src.NumHostsReady, + NumIdentitySources: src.NumIdentitySources, + NumMetadataSources: src.NumMetadataSources, + NumAuthorizationPolicies: src.NumAuthorizationPolicies, + NumResponseItems: src.NumResponseItems, + FestivalWristbandEnabled: src.FestivalWristbandEnabled, + } +} + +func convertStatusSummaryFrom(src v1beta1.Summary) AuthConfigStatusSummary { + return AuthConfigStatusSummary{ + Ready: src.Ready, + HostsReady: src.HostsReady, + NumHostsReady: src.NumHostsReady, + NumIdentitySources: src.NumIdentitySources, + NumMetadataSources: src.NumMetadataSources, + NumAuthorizationPolicies: src.NumAuthorizationPolicies, + NumResponseItems: src.NumResponseItems, + FestivalWristbandEnabled: src.FestivalWristbandEnabled, + } +} diff --git a/api/v1beta2/auth_config_webhook.go b/api/v1beta2/auth_config_webhook.go new file mode 100644 index 00000000..d7f69df1 --- /dev/null +++ b/api/v1beta2/auth_config_webhook.go @@ -0,0 +1,11 @@ +package v1beta2 + +import ( + ctrl "sigs.k8s.io/controller-runtime" +) + +func (a *AuthConfig) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(a). + Complete() +} diff --git a/deploy/authorino.yaml b/deploy/authorino.yaml index 6a387e4a..65d31eb7 100644 --- a/deploy/authorino.yaml +++ b/deploy/authorino.yaml @@ -26,7 +26,7 @@ spec: tls: enabled: $(TLS_ENABLED) certSecretRef: - name: authorino-server-cert # Kubernetes secret must contain `tls.crt` and `tls.key` entries + name: $(AUTHORINO_INSTANCE)-server-cert # Kubernetes secret must contain `tls.crt` and `tls.key` entries timeout: 0 # (in ms) - set to '0' to disable timeout of the ext-authz request controlled internally oidcServer: @@ -34,7 +34,7 @@ spec: tls: enabled: $(TLS_ENABLED) certSecretRef: - name: authorino-oidc-server-cert # Kubernetes secret must contain `tls.crt` and `tls.key` entries + name: $(AUTHORINO_INSTANCE)-oidc-server-cert # Kubernetes secret must contain `tls.crt` and `tls.key` entries ## Uncomment to customize settings of the metrics server # metrics: diff --git a/deploy/certs.yaml b/deploy/certs.yaml index 9f60e677..b314e659 100644 --- a/deploy/certs.yaml +++ b/deploy/certs.yaml @@ -1,24 +1,45 @@ apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + authorino-instance: $(AUTHORINO_INSTANCE) + name: $(AUTHORINO_INSTANCE)-ca-root + namespace: $(NAMESPACE) +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 kind: Certificate metadata: labels: - app: authorino - name: authorino-ca-cert + authorino-instance: $(AUTHORINO_INSTANCE) + name: $(AUTHORINO_INSTANCE)-ca-cert namespace: $(NAMESPACE) spec: commonName: '*.$(NAMESPACE).svc' isCA: true issuerRef: kind: Issuer - name: authorino-selfsigned-issuer - secretName: authorino-ca-cert + name: $(AUTHORINO_INSTANCE)-ca-root + secretName: $(AUTHORINO_INSTANCE)-ca-cert +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + authorino-instance: $(AUTHORINO_INSTANCE) + name: $(AUTHORINO_INSTANCE)-ca + namespace: $(NAMESPACE) +spec: + ca: + secretName: $(AUTHORINO_INSTANCE)-ca-cert --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: labels: - app: authorino - name: authorino-oidc-server-cert + authorino-instance: $(AUTHORINO_INSTANCE) + name: $(AUTHORINO_INSTANCE)-oidc-server-cert namespace: $(NAMESPACE) spec: dnsNames: @@ -27,15 +48,15 @@ spec: - $(AUTHORINO_INSTANCE)-authorino-oidc.$(NAMESPACE).svc.cluster.local issuerRef: kind: Issuer - name: authorino-ca-issuer - secretName: authorino-oidc-server-cert + name: $(AUTHORINO_INSTANCE)-ca + secretName: $(AUTHORINO_INSTANCE)-oidc-server-cert --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: labels: - app: authorino - name: authorino-server-cert + authorino-instance: $(AUTHORINO_INSTANCE) + name: $(AUTHORINO_INSTANCE)-server-cert namespace: $(NAMESPACE) spec: dnsNames: @@ -44,26 +65,5 @@ spec: - $(AUTHORINO_INSTANCE)-authorino-authorization.$(NAMESPACE).svc.cluster.local issuerRef: kind: Issuer - name: authorino-ca-issuer - secretName: authorino-server-cert ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - labels: - app: authorino - name: authorino-ca-issuer - namespace: $(NAMESPACE) -spec: - ca: - secretName: authorino-ca-cert ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - labels: - app: authorino - name: authorino-selfsigned-issuer - namespace: $(NAMESPACE) -spec: - selfSigned: {} + name: $(AUTHORINO_INSTANCE)-ca + secretName: $(AUTHORINO_INSTANCE)-server-cert diff --git a/install/crd/kustomization.yaml b/install/crd/kustomization.yaml index b5b8dc3c..6eb9f899 100644 --- a/install/crd/kustomization.yaml +++ b/install/crd/kustomization.yaml @@ -5,13 +5,10 @@ resources: - authorino.kuadrant.io_authconfigs.yaml # +kubebuilder:scaffold:crdkustomizeresource -# patchesStrategicMerge: -#- patches/webhook_in_authconfigs.yaml +patchesStrategicMerge: +- patches/webhook_in_authconfigs.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch -#- patches/cainjection_in_authconfigs.yaml -# +kubebuilder:scaffold:crdkustomizecainjectionpatch - patchesJson6902: - path: patches/oneof_in_authconfigs.yaml target: @@ -19,6 +16,3 @@ patchesJson6902: version: v1 kind: CustomResourceDefinition name: authconfigs.authorino.kuadrant.io - -configurations: -- kustomizeconfig.yaml diff --git a/install/crd/kustomizeconfig.yaml b/install/crd/kustomizeconfig.yaml deleted file mode 100644 index 6f83d9a9..00000000 --- a/install/crd/kustomizeconfig.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# This file is for teaching kustomize how to substitute name and namespace reference in CRD -nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: CustomResourceDefinition - group: apiextensions.k8s.io - path: spec/conversion/webhookClientConfig/service/name - -namespace: -- kind: CustomResourceDefinition - group: apiextensions.k8s.io - path: spec/conversion/webhookClientConfig/service/namespace - create: false - -varReference: -- path: metadata/annotations diff --git a/install/crd/patches/cainjection_in_authconfigs.yaml b/install/crd/patches/cainjection_in_authconfigs.yaml deleted file mode 100644 index 60bd77d0..00000000 --- a/install/crd/patches/cainjection_in_authconfigs.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -# CRD conversion requires k8s 1.13 or later. -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: authconfigs.authorino.kuadrant.io diff --git a/install/crd/patches/webhook_in_authconfigs.yaml b/install/crd/patches/webhook_in_authconfigs.yaml index bcb9cb1c..13d34bf4 100644 --- a/install/crd/patches/webhook_in_authconfigs.yaml +++ b/install/crd/patches/webhook_in_authconfigs.yaml @@ -1,17 +1,21 @@ -# The following patch enables conversion webhook for CRD -# CRD conversion requires k8s 1.13 or later. -apiVersion: apiextensions.k8s.io/v1beta1 +# The following patch applies the default strategy to convert between versions of the CRD +# Modify this file to patch the CRD for calling a proper webhook service. E.g.: +# +# spec: +# conversion: +# strategy: Webhook +# webhook: +# clientConfig: +# service: +# namespace: WEBHOOK_SERVICE_NAMESPACE +# name: WEBHOOK_SERVICE_NAME +# path: /convert +# conversionReviewVersions: +# - v1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: authconfigs.authorino.kuadrant.io spec: conversion: - strategy: Webhook - webhookClientConfig: - # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, - # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) - caBundle: Cg== - service: - namespace: system - name: webhook-service - path: /convert + strategy: None diff --git a/install/kustomization.yaml b/install/kustomization.yaml index 482b3d27..91a40509 100644 --- a/install/kustomization.yaml +++ b/install/kustomization.yaml @@ -1,5 +1,6 @@ # This is the main Kustomization file to install Authorino. -# It includes the installation of the CRD and RBAC required to later deploy an AUthorino instance. +# It includes the installation of the CRD and RBAC required to later deploy an Authorino instance. +# Warning! Modifications to this file or to its referenced parts may affect the bundle that is served by the Authorino Operator. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization @@ -7,7 +8,4 @@ resources: - crd - rbac -# Value of this field is prepended to the names of all resources. -# E.g. a deployment named "controller" becomes "authorino-controller". namePrefix: authorino- - diff --git a/install/manifests.yaml b/install/manifests.yaml index 0e988bb0..cc7e12d9 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -3,9 +3,10 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.9.0 - creationTimestamp: null name: authconfigs.authorino.kuadrant.io spec: + conversion: + strategy: None group: authorino.kuadrant.io names: kind: AuthConfig diff --git a/install/webhooks/certificate.yaml b/install/webhooks/certificate.yaml new file mode 100644 index 00000000..7087226d --- /dev/null +++ b/install/webhooks/certificate.yaml @@ -0,0 +1,21 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + namespace: system + name: webhooks-ca +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + namespace: system + name: webhook-server-cert +spec: + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: authorino-webhooks-ca + secretName: authorino-webhook-server-cert diff --git a/install/webhooks/deployment.yaml b/install/webhooks/deployment.yaml new file mode 100644 index 00000000..70a16f1e --- /dev/null +++ b/install/webhooks/deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: system + name: webhooks +spec: + selector: {} + template: + spec: + containers: + - name: webhooks + image: authorino:local + imagePullPolicy: IfNotPresent + command: + - authorino + - webhooks + ports: + - name: webhooks + containerPort: 9443 + - name: metrics + containerPort: 8080 + - name: healthz + containerPort: 8081 + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + resources: {} + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: authorino-webhook-server-cert diff --git a/install/webhooks/kustomization.yaml b/install/webhooks/kustomization.yaml new file mode 100644 index 00000000..5832780d --- /dev/null +++ b/install/webhooks/kustomization.yaml @@ -0,0 +1,104 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- certificate.yaml +- deployment.yaml +- service.yaml +- rbac.yaml +- ../crd + +patchesStrategicMerge: +- patches/webhook_in_authconfigs.yaml + +commonLabels: + app: authorino + authorino-component: authorino-webhooks + +namespace: authorino-webhooks + +namePrefix: authorino- + +replacements: +- source: + fieldPath: .metadata.namespace + group: cert-manager.io + kind: Certificate + name: authorino-webhook-server-cert + version: v1 + targets: + - fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + create: true + delimiter: / + select: + kind: CustomResourceDefinition +- source: + fieldPath: .metadata.name + group: cert-manager.io + kind: Certificate + name: authorino-webhook-server-cert + version: v1 + targets: + - fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + create: true + delimiter: / + index: 1 + select: + kind: CustomResourceDefinition +- source: + fieldPath: .metadata.name + kind: Service + name: authorino-webhooks + version: v1 + targets: + - fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + create: true + delimiter: . + select: + group: cert-manager.io + kind: Certificate + name: authorino-webhook-server-cert + version: v1 + - fieldPaths: + - .spec.conversion.webhook.clientConfig.service.name + options: + create: true + select: + kind: CustomResourceDefinition +- source: + fieldPath: .metadata.namespace + kind: Service + name: authorino-webhooks + version: v1 + targets: + - fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + create: true + delimiter: . + index: 1 + select: + group: cert-manager.io + kind: Certificate + name: authorino-webhook-server-cert + version: v1 + - fieldPaths: + - .spec.conversion.webhook.clientConfig.service.namespace + options: + create: true + select: + kind: CustomResourceDefinition + - fieldPaths: + - subjects.0.namespace + select: + group: rbac.authorization.k8s.io + kind: RoleBinding + name: authorino-webhooks-manager diff --git a/install/webhooks/patches/webhook_in_authconfigs.yaml b/install/webhooks/patches/webhook_in_authconfigs.yaml new file mode 100644 index 00000000..b1c0a10b --- /dev/null +++ b/install/webhooks/patches/webhook_in_authconfigs.yaml @@ -0,0 +1,19 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: authconfigs.authorino.kuadrant.io + annotations: + cert-manager.io/inject-ca-from: WEBHOOK_CERTIFICATE_NAMESPACE/WEBHOOK_CERTIFICATE_NAME +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: WEBHOOK_SERVICE_NAMESPACE + name: WEBHOOK_SERVICE_NAME + path: /convert + conversionReviewVersions: + - v1beta1 + - v1beta2 diff --git a/install/webhooks/rbac.yaml b/install/webhooks/rbac.yaml new file mode 100644 index 00000000..54343754 --- /dev/null +++ b/install/webhooks/rbac.yaml @@ -0,0 +1,58 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: webhooks-manager-role +rules: +- apiGroups: + - authorino.kuadrant.io + resources: + - authconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - authorino.kuadrant.io + resources: + - authconfigs/status + verbs: + - get + - patch + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - list + - update +- apiGroups: + - "" + resources: + - configmaps + - events + verbs: + - create + - get + - list + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: system + name: webhooks-manager +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: authorino-webhooks-manager-role +subjects: +- kind: ServiceAccount + name: default + namespace: SERVICE_NAMESPACE diff --git a/install/webhooks/service.yaml b/install/webhooks/service.yaml new file mode 100644 index 00000000..974a7898 --- /dev/null +++ b/install/webhooks/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + namespace: system + name: webhooks +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: {} diff --git a/main.go b/main.go index 530aa19a..ddddc067 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "crypto/sha256" "crypto/tls" "encoding/hex" @@ -26,8 +27,8 @@ import ( "os" "time" - "github.com/go-logr/logr" - api "github.com/kuadrant/authorino/api/v1beta1" + v1beta1 "github.com/kuadrant/authorino/api/v1beta1" + v1beta2 "github.com/kuadrant/authorino/api/v1beta2" "github.com/kuadrant/authorino/controllers" "github.com/kuadrant/authorino/pkg/evaluators" "github.com/kuadrant/authorino/pkg/health" @@ -39,10 +40,15 @@ import ( "github.com/kuadrant/authorino/pkg/utils" envoy_auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/go-logr/logr" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" "github.com/spf13/pflag" + otel_grpc "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + otel_http "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + otel_propagation "go.opentelemetry.io/otel/propagation" "google.golang.org/grpc" "google.golang.org/grpc/credentials" healthpb "google.golang.org/grpc/health/grpc_health_v1" @@ -53,11 +59,6 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" - - otel_grpc "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - otel_http "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.opentelemetry.io/otel" - otel_propagation "go.opentelemetry.io/otel/propagation" // +kubebuilder:scaffold:imports ) @@ -70,12 +71,39 @@ var ( // ldflags version string - // option flags + scheme = runtime.NewScheme() + logger logr.Logger +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(v1beta1.AddToScheme(scheme)) + utilruntime.Must(v1beta2.AddToScheme(scheme)) +} + +type logOptions struct { + level string + mode string +} + +type telemetryOptions struct { + tracingServiceEndpoint string + tracingServiceInsecure bool + tracingServiceTags []string +} + +type commonServerOptions struct { + log logOptions + metricsAddr string + healthProbeAddr string + telemetry telemetryOptions +} + +type authServerOptions struct { + commonServerOptions watchNamespace string watchedAuthConfigLabelSelector string watchedSecretLabelSelector string - logLevel string - logMode string timeout int extAuthGRPCPort int extAuthHTTPPort int @@ -86,226 +114,324 @@ var ( oidcTLSCertKeyPath string evaluatorCacheSize int deepMetricsEnabled bool - metricsAddr string - healthProbeAddr string + webhookServicePort int enableLeaderElection bool maxHttpRequestBodySize int64 - tracingServiceEndpoint string - tracingServiceInsecure bool - tracingServiceTags []string +} - scheme = runtime.NewScheme() +type webhookServerOptions struct { + commonServerOptions + port int +} - logger logr.Logger -) +type keyAuthServerOptions struct{} +type keyWebhookServerOptions struct{} -func init() { - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(api.AddToScheme(scheme)) - // +kubebuilder:scaffold:scheme +func main() { + authServerOpts := &authServerOptions{} + webhookServerOpts := &webhookServerOptions{} + + cmdRoot := rootCmd() + + cmdRoot.AddCommand( + authServerCmd(authServerOpts), + webhookServerCmd(webhookServerOpts), + versionCmd(), + ) + + ctx := context.WithValue(context.TODO(), keyAuthServerOptions{}, authServerOpts) + ctx = context.WithValue(ctx, keyWebhookServerOptions{}, webhookServerOpts) + + if err := cmdRoot.ExecuteContext(ctx); err != nil { + fmt.Println("error: ", err) + os.Exit(1) + } } -func main() { - cmdRoot := &cobra.Command{ +func rootCmd() *cobra.Command { + return &cobra.Command{ Use: "authorino", Short: "Authorino is a Kubernetes-native authorization server.", } +} - cmdServer := &cobra.Command{ +func authServerCmd(opts *authServerOptions) *cobra.Command { + cmd := &cobra.Command{ Use: "server", Short: "Runs the authorization server", - Run: run, - } - - cmdServer.PersistentFlags().StringVar(&watchNamespace, "watch-namespace", utils.EnvVar("WATCH_NAMESPACE", ""), "Kubernetes namespace to watch") - cmdServer.PersistentFlags().StringVar(&watchedAuthConfigLabelSelector, "auth-config-label-selector", utils.EnvVar("AUTH_CONFIG_LABEL_SELECTOR", ""), "Kubernetes label selector to filter AuthConfig resources to watch") - cmdServer.PersistentFlags().StringVar(&watchedSecretLabelSelector, "secret-label-selector", utils.EnvVar("SECRET_LABEL_SELECTOR", "authorino.kuadrant.io/managed-by=authorino"), "Kubernetes label selector to filter Secret resources to watch") - cmdServer.PersistentFlags().StringVar(&logLevel, "log-level", utils.EnvVar("LOG_LEVEL", "info"), "Log level") - cmdServer.PersistentFlags().StringVar(&logMode, "log-mode", utils.EnvVar("LOG_MODE", "production"), "Log mode") - cmdServer.PersistentFlags().IntVar(&timeout, "timeout", utils.EnvVar("TIMEOUT", 0), "Server timeout - in milliseconds") - cmdServer.PersistentFlags().IntVar(&extAuthGRPCPort, "ext-auth-grpc-port", utils.EnvVar("EXT_AUTH_GRPC_PORT", 50051), "Port number of authorization server - gRPC interface") - cmdServer.PersistentFlags().IntVar(&extAuthHTTPPort, "ext-auth-http-port", utils.EnvVar("EXT_AUTH_HTTP_PORT", 5001), "Port number of authorization server - raw HTTP interface") - cmdServer.PersistentFlags().StringVar(&tlsCertPath, "tls-cert", utils.EnvVar("TLS_CERT", ""), "Path to the public TLS server certificate file in the file system - authorization server") - cmdServer.PersistentFlags().StringVar(&tlsCertKeyPath, "tls-cert-key", utils.EnvVar("TLS_CERT_KEY", ""), "Path to the private TLS server certificate key file in the file system - authorization server") - cmdServer.PersistentFlags().IntVar(&oidcHTTPPort, "oidc-http-port", utils.EnvVar("OIDC_HTTP_PORT", 8083), "Port number of OIDC Discovery server for Festival Wristband tokens") - cmdServer.PersistentFlags().StringVar(&oidcTLSCertPath, "oidc-tls-cert", utils.EnvVar("OIDC_TLS_CERT", ""), "Path to the public TLS server certificate file in the file system - Festival Wristband OIDC Discovery server") - cmdServer.PersistentFlags().StringVar(&oidcTLSCertKeyPath, "oidc-tls-cert-key", utils.EnvVar("OIDC_TLS_CERT_KEY", ""), "Path to the private TLS server certificate key file in the file system - Festival Wristband OIDC Discovery server") - cmdServer.PersistentFlags().IntVar(&evaluatorCacheSize, "evaluator-cache-size", utils.EnvVar("EVALUATOR_CACHE_SIZE", 1), "Cache size of each Authorino evaluator if enabled in the AuthConfig - in megabytes") - cmdServer.PersistentFlags().BoolVar(&deepMetricsEnabled, "deep-metrics-enabled", utils.EnvVar("DEEP_METRICS_ENABLED", false), "Enable deep metrics at the level of each evaluator when requested in the AuthConfig, exported by the metrics server") - cmdServer.PersistentFlags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The network address the metrics endpoint binds to") - cmdServer.PersistentFlags().StringVar(&healthProbeAddr, "health-probe-addr", ":8081", "The network address the health probe endpoint binds to") - cmdServer.PersistentFlags().BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for status updater - ensures only one instance of Authorino tries to update the status of reconciled resources") - cmdServer.PersistentFlags().Int64Var(&maxHttpRequestBodySize, "max-http-request-body-size", utils.EnvVar("MAX_HTTP_REQUEST_BODY_SIZE", int64(8192)), "Maximum size of the body of requests accepted in the raw HTTP interface of the authorization server - in bytes") - cmdServer.PersistentFlags().StringVar(&tracingServiceEndpoint, "tracing-service-endpoint", "", "Endpoint URL of the tracing exporter service - use either 'rpc://' or 'http://' scheme") - cmdServer.PersistentFlags().BoolVar(&tracingServiceInsecure, "tracing-service-insecure", false, "Disable TLS for the tracing service connection") - cmdServer.PersistentFlags().StringArrayVar(&tracingServiceTags, "tracing-service-tag", []string{}, "Fixed key=value tag to add to emitted traces") - - cmdVersion := &cobra.Command{ + Run: runAuthorizationServer, + } + + cmd.PersistentFlags().StringVar(&opts.watchNamespace, "watch-namespace", utils.EnvVar("WATCH_NAMESPACE", ""), "Kubernetes namespace to watch") + cmd.PersistentFlags().StringVar(&opts.watchedAuthConfigLabelSelector, "auth-config-label-selector", utils.EnvVar("AUTH_CONFIG_LABEL_SELECTOR", ""), "Kubernetes label selector to filter AuthConfig resources to watch") + cmd.PersistentFlags().StringVar(&opts.watchedSecretLabelSelector, "secret-label-selector", utils.EnvVar("SECRET_LABEL_SELECTOR", "authorino.kuadrant.io/managed-by=authorino"), "Kubernetes label selector to filter Secret resources to watch") + cmd.PersistentFlags().IntVar(&opts.timeout, "timeout", utils.EnvVar("TIMEOUT", 0), "Server timeout - in milliseconds") + cmd.PersistentFlags().IntVar(&opts.extAuthGRPCPort, "ext-auth-grpc-port", utils.EnvVar("EXT_AUTH_GRPC_PORT", 50051), "Port number of authorization server - gRPC interface") + cmd.PersistentFlags().IntVar(&opts.extAuthHTTPPort, "ext-auth-http-port", utils.EnvVar("EXT_AUTH_HTTP_PORT", 5001), "Port number of authorization server - raw HTTP interface") + cmd.PersistentFlags().StringVar(&opts.tlsCertPath, "tls-cert", utils.EnvVar("TLS_CERT", ""), "Path to the public TLS server certificate file in the file system - authorization server") + cmd.PersistentFlags().StringVar(&opts.tlsCertKeyPath, "tls-cert-key", utils.EnvVar("TLS_CERT_KEY", ""), "Path to the private TLS server certificate key file in the file system - authorization server") + cmd.PersistentFlags().IntVar(&opts.oidcHTTPPort, "oidc-http-port", utils.EnvVar("OIDC_HTTP_PORT", 8083), "Port number of OIDC Discovery server for Festival Wristband tokens") + cmd.PersistentFlags().StringVar(&opts.oidcTLSCertPath, "oidc-tls-cert", utils.EnvVar("OIDC_TLS_CERT", ""), "Path to the public TLS server certificate file in the file system - Festival Wristband OIDC Discovery server") + cmd.PersistentFlags().StringVar(&opts.oidcTLSCertKeyPath, "oidc-tls-cert-key", utils.EnvVar("OIDC_TLS_CERT_KEY", ""), "Path to the private TLS server certificate key file in the file system - Festival Wristband OIDC Discovery server") + cmd.PersistentFlags().IntVar(&opts.evaluatorCacheSize, "evaluator-cache-size", utils.EnvVar("EVALUATOR_CACHE_SIZE", 1), "Cache size of each Authorino evaluator if enabled in the AuthConfig - in megabytes") + cmd.PersistentFlags().BoolVar(&opts.deepMetricsEnabled, "deep-metrics-enabled", utils.EnvVar("DEEP_METRICS_ENABLED", false), "Enable deep metrics at the level of each evaluator when requested in the AuthConfig, exported by the metrics server") + cmd.PersistentFlags().IntVar(&opts.webhookServicePort, "webhook-service-port", 9443, "Port number of the webhook server") + cmd.PersistentFlags().BoolVar(&opts.enableLeaderElection, "enable-leader-election", false, "Enable leader election for status updater - ensures only one instance of Authorino tries to update the status of reconciled resources") + cmd.PersistentFlags().Int64Var(&opts.maxHttpRequestBodySize, "max-http-request-body-size", utils.EnvVar("MAX_HTTP_REQUEST_BODY_SIZE", int64(8192)), "Maximum size of the body of requests accepted in the raw HTTP interface of the authorization server - in bytes") + registerCommonServerOptions(cmd, &opts.commonServerOptions) + + return cmd +} + +func webhookServerCmd(opts *webhookServerOptions) *cobra.Command { + cmd := &cobra.Command{ + Use: "webhooks", + Short: "Runs the webhook server", + Run: runWebhookServer, + } + + cmd.PersistentFlags().IntVar(&opts.port, "port", 9443, "Port number of the webhook server") + registerCommonServerOptions(cmd, &opts.commonServerOptions) + + return cmd +} + +func versionCmd() *cobra.Command { + return &cobra.Command{ Use: "version", Short: "Prints the Authorino version info", Run: printVersion, } +} - cmdRoot.AddCommand(cmdServer, cmdVersion) - - if err := cmdRoot.Execute(); err != nil { - fmt.Println("error: ", err) - os.Exit(1) - } +func registerCommonServerOptions(cmd *cobra.Command, opts *commonServerOptions) { + cmd.PersistentFlags().StringVar(&opts.log.level, "log-level", utils.EnvVar("LOG_LEVEL", "info"), "Log level") + cmd.PersistentFlags().StringVar(&opts.log.mode, "log-mode", utils.EnvVar("LOG_MODE", "production"), "Log mode") + cmd.PersistentFlags().StringVar(&opts.metricsAddr, "metrics-addr", ":8080", "The network address the metrics endpoint binds to") + cmd.PersistentFlags().StringVar(&opts.healthProbeAddr, "health-probe-addr", ":8081", "The network address the health probe endpoint binds to") + cmd.PersistentFlags().StringVar(&opts.telemetry.tracingServiceEndpoint, "tracing-service-endpoint", "", "Endpoint URL of the tracing exporter service - use either 'rpc://' or 'http://' scheme") + cmd.PersistentFlags().BoolVar(&opts.telemetry.tracingServiceInsecure, "tracing-service-insecure", false, "Disable TLS for the tracing service connection") + cmd.PersistentFlags().StringArrayVar(&opts.telemetry.tracingServiceTags, "tracing-service-tag", []string{}, "Fixed key=value tag to add to emitted traces") } -func run(cmd *cobra.Command, _ []string) { - logOpts := log.Options{Level: log.ToLogLevel(logLevel), Mode: log.ToLogMode(logMode)} - logger = log.NewLogger(logOpts).WithName("authorino") - log.SetLogger(logger, logOpts) +func runAuthorizationServer(cmd *cobra.Command, _ []string) { + opts := cmd.Context().Value(keyAuthServerOptions{}).(*authServerOptions) - logger.Info("booting up authorino", "version", version) + setup(cmd, opts.log, opts.telemetry) - var flags []interface{} - cmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) { - flags = append(flags, flag.Name, flag.Value.String()) - }) + // global options + evaluators.EvaluatorCacheSize = opts.evaluatorCacheSize + metrics.DeepMetricsEnabled = opts.deepMetricsEnabled - logger.V(1).Info("setting up with options", flags...) + // creates the index of authconfigs + index := index.NewIndex() + + // starts authorization server + startExtAuthServerGRPC(index, *opts) + startExtAuthServerHTTP(index, *opts) - evaluators.EvaluatorCacheSize = evaluatorCacheSize - metrics.DeepMetricsEnabled = deepMetricsEnabled + // starts the oidc discovery server + startOIDCServer(index, *opts) - managerOptions := ctrl.Options{ + baseManagerOptions := ctrl.Options{ Scheme: scheme, - MetricsBindAddress: metricsAddr, - HealthProbeBindAddress: healthProbeAddr, - Port: 9443, + Port: opts.webhookServicePort, + MetricsBindAddress: opts.metricsAddr, + HealthProbeBindAddress: opts.healthProbeAddr, LeaderElection: false, } - - if watchNamespace != "" { - managerOptions.Namespace = watchNamespace + if opts.watchNamespace != "" { + baseManagerOptions.Namespace = opts.watchNamespace } - telemetryLogger := logger.WithName("telemetry") - otel.SetLogger(telemetryLogger) - otel.SetErrorHandler(&trace.ErrorHandler{Logger: telemetryLogger}) - - if tracingServiceEndpoint != "" { - tp, err := trace.CreateTraceProvider(trace.Config{ - Endpoint: tracingServiceEndpoint, - Insecure: tracingServiceInsecure, - Version: version, - Tags: tracingServiceTags, - }) - if err != nil { - logger.Error(err, "unable to set trace provider") - } else { - otel.SetTracerProvider(tp) - } - } - - otel.SetTextMapPropagator(otel_propagation.NewCompositeTextMapPropagator(otel_propagation.TraceContext{}, otel_propagation.Baggage{})) - - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), managerOptions) + // sets up the reconciliation manager + mgr, err := setupManager(baseManagerOptions) if err != nil { - logger.Error(err, "unable to start manager") + logger.Error(err, "failed to setup reconciliation manager") os.Exit(1) } - index := index.NewIndex() statusReport := controllers.NewStatusReportMap() controllerLogger := log.WithName("controller-runtime").WithName("manager").WithName("controller") - // sets up the auth config reconciler + // sets up the authconfig reconciler authConfigReconciler := &controllers.AuthConfigReconciler{ Client: mgr.GetClient(), Index: index, StatusReport: statusReport, Logger: controllerLogger.WithName("authconfig"), Scheme: mgr.GetScheme(), - LabelSelector: controllers.ToLabelSelector(watchedAuthConfigLabelSelector), - Namespace: watchNamespace, + LabelSelector: controllers.ToLabelSelector(opts.watchedAuthConfigLabelSelector), + Namespace: opts.watchNamespace, } if err = authConfigReconciler.SetupWithManager(mgr); err != nil { - logger.Error(err, "unable to create controller", "controller", "authconfig") + logger.Error(err, "failed to setup controller", "controller", "authconfig") os.Exit(1) } - // sets up secret reconciler + // authconfig readiness check + readinessCheck := health.NewHandler(controllers.AuthConfigsReadyzSubpath, health.Observe(authConfigReconciler)) + if err := mgr.AddReadyzCheck(controllers.AuthConfigsReadyzSubpath, readinessCheck.HandleReadyzCheck); err != nil { + logger.Error(err, "failed to setup reconciliation readiness check") + os.Exit(1) + } + + // sets up the secret reconciler if err = (&controllers.SecretReconciler{ Client: mgr.GetClient(), Logger: controllerLogger.WithName("secret"), Scheme: mgr.GetScheme(), Index: index, - LabelSelector: controllers.ToLabelSelector(watchedSecretLabelSelector), - Namespace: watchNamespace, + LabelSelector: controllers.ToLabelSelector(opts.watchedSecretLabelSelector), + Namespace: opts.watchNamespace, }).SetupWithManager(mgr); err != nil { - logger.Error(err, "unable to create controller", "controller", "secret") - os.Exit(1) - } - - // +kubebuilder:scaffold:builder - - startExtAuthServerGRPC(index) - startExtAuthServerHTTP(index) - startOIDCServer(index) - - if err := mgr.AddMetricsExtraHandler("/server-metrics", promhttp.Handler()); err != nil { - logger.Error(err, "unable to set up controller metrics server") - os.Exit(1) - } - - if err := mgr.AddHealthzCheck("ping", healthz.Ping); err != nil { - logger.Error(err, "unable to set up controller health check") - os.Exit(1) - } - - readinessCheck := health.NewHandler(controllers.AuthConfigsReadyzSubpath, health.Observe(authConfigReconciler)) - if err := mgr.AddReadyzCheck(controllers.AuthConfigsReadyzSubpath, readinessCheck.HandleReadyzCheck); err != nil { - logger.Error(err, "unable to set up controller readiness check") + logger.Error(err, "failed to setup controller", "controller", "secret") os.Exit(1) } + // starts the reconciliation manager signalHandler := ctrl.SetupSignalHandler() - - logger.Info("starting manager") - + logger.Info("starting reconciliation manager") go func() { if err := mgr.Start(signalHandler); err != nil { - logger.Error(err, "problem running manager") + logger.Error(err, "failed to start reconciliation manager") os.Exit(1) } }() - // status update manager - leaderElectionId := sha256.Sum256([]byte(watchedAuthConfigLabelSelector)) - managerOptions.LeaderElection = enableLeaderElection - managerOptions.LeaderElectionID = fmt.Sprintf("%v.%v", hex.EncodeToString(leaderElectionId[:4]), leaderElectionIDSuffix) - managerOptions.MetricsBindAddress = "0" - managerOptions.HealthProbeBindAddress = "0" - statusUpdateManager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), managerOptions) + // sets up the status update manager + leaderElectionId := sha256.Sum256([]byte(opts.watchedAuthConfigLabelSelector)) + statusUpdaterOptions := baseManagerOptions + statusUpdaterOptions.MetricsBindAddress = "0" // disabled so it does not clash with the reconciliation manager + statusUpdaterOptions.HealthProbeBindAddress = "0" // disabled so it does not clash with the reconciliation manager + statusUpdaterOptions.LeaderElection = opts.enableLeaderElection + statusUpdaterOptions.LeaderElectionID = fmt.Sprintf("%v.%v", hex.EncodeToString(leaderElectionId[:4]), leaderElectionIDSuffix) + statusUpdateManager, err := setupManager(statusUpdaterOptions) if err != nil { - logger.Error(err, "unable to start status update manager") + logger.Error(err, "failed to setup status update manager") os.Exit(1) } - // sets up auth config status update controller + // sets up the authconfig status update controller if err = (&controllers.AuthConfigStatusUpdater{ Client: statusUpdateManager.GetClient(), Logger: controllerLogger.WithName("authconfig").WithName("statusupdater"), StatusReport: statusReport, - LabelSelector: controllers.ToLabelSelector(watchedAuthConfigLabelSelector), + LabelSelector: controllers.ToLabelSelector(opts.watchedAuthConfigLabelSelector), }).SetupWithManager(statusUpdateManager); err != nil { - logger.Error(err, "unable to create controller", "controller", "authconfigstatusupdate") + logger.Error(err, "failed to create controller", "controller", "authconfigstatusupdate") } + // starts the status update manager logger.Info("starting status update manager") - if err := statusUpdateManager.Start(signalHandler); err != nil { - logger.Error(err, "problem running status update manager") + logger.Error(err, "failed to start status update manager") os.Exit(1) } } -func startExtAuthServerGRPC(authConfigIndex index.Index) { - lis, err := listen(extAuthGRPCPort) +func runWebhookServer(cmd *cobra.Command, _ []string) { + opts := cmd.Context().Value(keyWebhookServerOptions{}).(*webhookServerOptions) + + setup(cmd, opts.log, opts.telemetry) + + // sets up the webhook manager + mgr, err := setupManager(ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: opts.metricsAddr, + HealthProbeBindAddress: opts.healthProbeAddr, + LeaderElection: true, + LeaderElectionID: fmt.Sprintf("670aa2de.%s", leaderElectionIDSuffix), + Port: opts.port, + }) + if err != nil { + logger.Error(err, "failed to setup webhook manager") + os.Exit(1) + } + + // sets up the authconfig webhook + if err := (&v1beta2.AuthConfig{}).SetupWebhookWithManager(mgr); err != nil { + logger.Error(err, "failed to setup authconfig webhook") + os.Exit(1) + } + + // starts the webhook manager + signalHandler := ctrl.SetupSignalHandler() + logger.Info("starting webhook manager") + if err := mgr.Start(signalHandler); err != nil { + logger.Error(err, "failed to start webhook manager") + os.Exit(1) + } +} + +func setup(cmd *cobra.Command, log logOptions, telemetry telemetryOptions) { + setupLogger(log) + + logger.Info("booting up authorino", "version", version, "cmd", cmd.Use) + + // log the command-line args + if logger.V(1).Enabled() { + var flags []interface{} + cmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) { + flags = append(flags, flag.Name, flag.Value.String()) + }) + logger.V(1).Info("setting up with options", flags...) + } + + setupTelemetryServices(telemetry) +} + +func setupLogger(opts logOptions) { + logOpts := log.Options{Level: log.ToLogLevel(opts.level), Mode: log.ToLogMode(opts.mode)} + logger = log.NewLogger(logOpts).WithName("authorino") + log.SetLogger(logger, logOpts) +} + +func setupTelemetryServices(opts telemetryOptions) { + telemetryLogger := logger.WithName("telemetry") + otel.SetLogger(telemetryLogger) + otel.SetErrorHandler(&trace.ErrorHandler{Logger: telemetryLogger}) + + if opts.tracingServiceEndpoint != "" { + tp, err := trace.CreateTraceProvider(trace.Config{ + Endpoint: opts.tracingServiceEndpoint, + Insecure: opts.tracingServiceInsecure, + Version: version, + Tags: opts.tracingServiceTags, + }) + if err != nil { + telemetryLogger.Error(err, "unable to set trace provider") + } else { + otel.SetTracerProvider(tp) + } + } + + otel.SetTextMapPropagator(otel_propagation.NewCompositeTextMapPropagator(otel_propagation.TraceContext{}, otel_propagation.Baggage{})) +} + +func setupManager(options ctrl.Options) (ctrl.Manager, error) { + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) + if err != nil { + return nil, err + } + + if options.MetricsBindAddress != "0" { + if err := mgr.AddMetricsExtraHandler("/server-metrics", promhttp.Handler()); err != nil { + return nil, err + } + } + + if options.HealthProbeBindAddress != "0" { + if err := mgr.AddHealthzCheck("ping", healthz.Ping); err != nil { + return nil, err + } + } + + return mgr, nil +} + +func startExtAuthServerGRPC(authConfigIndex index.Index, opts authServerOptions) { + lis, err := listen(opts.extAuthGRPCPort) if err != nil { logger.Error(err, "failed to obtain port for the grpc auth service") @@ -323,10 +449,10 @@ func startExtAuthServerGRPC(authConfigIndex index.Index) { grpc.ChainUnaryInterceptor(grpc_prometheus.UnaryServerInterceptor, otel_grpc.UnaryServerInterceptor()), } - tlsEnabled := tlsCertPath != "" && tlsCertKeyPath != "" + tlsEnabled := opts.tlsCertPath != "" && opts.tlsCertKeyPath != "" if tlsEnabled { - if tlsCert, err := tls.LoadX509KeyPair(tlsCertPath, tlsCertKeyPath); err != nil { + if tlsCert, err := tls.LoadX509KeyPair(opts.tlsCertPath, opts.tlsCertKeyPath); err != nil { logger.Error(err, "failed to load tls cert for the grpc auth service") os.Exit(1) } else { @@ -342,13 +468,13 @@ func startExtAuthServerGRPC(authConfigIndex index.Index) { grpcServer := grpc.NewServer(grpcServerOpts...) reflection.Register(grpcServer) - envoy_auth.RegisterAuthorizationServer(grpcServer, &service.AuthService{Index: authConfigIndex, Timeout: timeoutMs()}) + envoy_auth.RegisterAuthorizationServer(grpcServer, &service.AuthService{Index: authConfigIndex, Timeout: timeoutMs(opts.timeout)}) healthpb.RegisterHealthServer(grpcServer, &service.HealthService{}) grpc_prometheus.Register(grpcServer) grpc_prometheus.EnableHandlingTimeHistogram() go func() { - logger.Info("starting grpc auth service", "port", extAuthGRPCPort, "tls", tlsEnabled) + logger.Info("starting grpc auth service", "port", opts.extAuthGRPCPort, "tls", tlsEnabled) if err := grpcServer.Serve(lis); err != nil { logger.Error(err, "failed to start grpc auth service") @@ -357,12 +483,12 @@ func startExtAuthServerGRPC(authConfigIndex index.Index) { }() } -func startExtAuthServerHTTP(authConfigIndex index.Index) { - startHTTPService("auth", extAuthHTTPPort, service.HTTPAuthorizationBasePath, tlsCertPath, tlsCertKeyPath, service.NewAuthService(authConfigIndex, timeoutMs(), maxHttpRequestBodySize)) +func startExtAuthServerHTTP(authConfigIndex index.Index, opts authServerOptions) { + startHTTPService("auth", opts.extAuthHTTPPort, service.HTTPAuthorizationBasePath, opts.tlsCertPath, opts.tlsCertKeyPath, service.NewAuthService(authConfigIndex, timeoutMs(opts.timeout), opts.maxHttpRequestBodySize)) } -func startOIDCServer(authConfigIndex index.Index) { - startHTTPService("oidc", oidcHTTPPort, service.OIDCBasePath, oidcTLSCertPath, oidcTLSCertKeyPath, &service.OidcService{Index: authConfigIndex}) +func startOIDCServer(authConfigIndex index.Index, opts authServerOptions) { + startHTTPService("oidc", opts.oidcHTTPPort, service.OIDCBasePath, opts.oidcTLSCertPath, opts.oidcTLSCertKeyPath, &service.OidcService{Index: authConfigIndex}) } func startHTTPService(name string, port int, basePath, tlsCertPath, tlsCertKeyPath string, handler http.Handler) { @@ -427,7 +553,7 @@ func fetchEnv(key string, def interface{}) string { } } -func timeoutMs() time.Duration { +func timeoutMs(timeout int) time.Duration { return time.Duration(timeout) * time.Millisecond } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 3fff3449..fd5b8109 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -40,6 +40,15 @@ func SliceContains[T comparable](s []T, val T) bool { return false } +// Map applies the given mapper function to each element in the input slice and returns a new slice with the results. +func Map[T, U any](slice []T, f func(T) U) []U { + arr := make([]U, len(slice)) + for i, e := range slice { + arr[i] = f(e) + } + return arr +} + func CopyMap[T comparable, U any](m map[T]U) map[T]U { m2 := make(map[T]U) for k, v := range m { From 7d4603cdfd05731054990f8f6e8cd031dcf736e9 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Thu, 27 Jul 2023 18:14:13 +0200 Subject: [PATCH 03/35] fix one-of restriction for pattern expression of pattern ref It cannot require 'value' or the conversion will fail when the value is an empty string (due to JSON marshalling/unmarshalling involved). --- api/v1beta2/auth_config_conversion.go | 10 ++++++++-- install/crd/patches/oneof_in_authconfigs.yaml | 12 ++++++------ install/manifests.yaml | 6 ------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go index 0a411efb..efe0d208 100644 --- a/api/v1beta2/auth_config_conversion.go +++ b/api/v1beta2/auth_config_conversion.go @@ -1000,9 +1000,12 @@ func convertStatusFrom(src v1beta1.AuthConfigStatus) AuthConfigStatus { } func convertStatusSummaryTo(src AuthConfigStatusSummary) v1beta1.Summary { + hostsReady := make([]string, len(src.HostsReady)) + copy(hostsReady, src.HostsReady) + return v1beta1.Summary{ Ready: src.Ready, - HostsReady: src.HostsReady, + HostsReady: hostsReady, NumHostsReady: src.NumHostsReady, NumIdentitySources: src.NumIdentitySources, NumMetadataSources: src.NumMetadataSources, @@ -1013,9 +1016,12 @@ func convertStatusSummaryTo(src AuthConfigStatusSummary) v1beta1.Summary { } func convertStatusSummaryFrom(src v1beta1.Summary) AuthConfigStatusSummary { + hostsReady := make([]string, len(src.HostsReady)) + copy(hostsReady, src.HostsReady) + return AuthConfigStatusSummary{ Ready: src.Ready, - HostsReady: src.HostsReady, + HostsReady: hostsReady, NumHostsReady: src.NumHostsReady, NumIdentitySources: src.NumIdentitySources, NumMetadataSources: src.NumMetadataSources, diff --git a/install/crd/patches/oneof_in_authconfigs.yaml b/install/crd/patches/oneof_in_authconfigs.yaml index c1f38113..0e758374 100644 --- a/install/crd/patches/oneof_in_authconfigs.yaml +++ b/install/crd/patches/oneof_in_authconfigs.yaml @@ -101,7 +101,7 @@ operator: {} selector: {} value: {} - required: [operator, selector, value] + required: [operator, selector] - op: add path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/when/items/oneOf @@ -113,7 +113,7 @@ operator: {} selector: {} value: {} - required: [operator, selector, value] + required: [operator, selector] - op: add path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/identity/items/properties/when/items/oneOf @@ -125,7 +125,7 @@ operator: {} selector: {} value: {} - required: [operator, selector, value] + required: [operator, selector] - op: add path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/metadata/items/properties/when/items/oneOf @@ -137,7 +137,7 @@ operator: {} selector: {} value: {} - required: [operator, selector, value] + required: [operator, selector] - op: add path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/authorization/items/properties/when/items/oneOf @@ -149,7 +149,7 @@ operator: {} selector: {} value: {} - required: [operator, selector, value] + required: [operator, selector] - op: add path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/response/items/properties/when/items/oneOf @@ -161,4 +161,4 @@ operator: {} selector: {} value: {} - required: [operator, selector, value] + required: [operator, selector] diff --git a/install/manifests.yaml b/install/manifests.yaml index cc7e12d9..edec7fba 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -332,7 +332,6 @@ spec: required: - operator - selector - - value properties: operator: description: 'The binary operator to be applied to @@ -692,7 +691,6 @@ spec: required: - operator - selector - - value properties: operator: description: 'The binary operator to be applied to the @@ -1585,7 +1583,6 @@ spec: required: - operator - selector - - value properties: operator: description: 'The binary operator to be applied to the @@ -1973,7 +1970,6 @@ spec: required: - operator - selector - - value properties: operator: description: 'The binary operator to be applied to the @@ -2200,7 +2196,6 @@ spec: required: - operator - selector - - value properties: operator: description: 'The binary operator to be applied to the @@ -2345,7 +2340,6 @@ spec: required: - operator - selector - - value properties: operator: description: 'The binary operator to be applied to the content From 3aada04e051c1f5288befd6e206bc0e60aff27ce Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 29 Aug 2023 12:45:59 +0200 Subject: [PATCH 04/35] Conversion webhook service managed by the Operator - Let the Operator deploy the default conversion webhook service - Substitute the webhook image with the one built locally - Keep the patches to the AuthConfig CRD performed by the Operator to activate the webhook before re-applying the CRD with local changes --- Makefile | 33 +++--- .../crd/patches/webhook_in_authconfigs.yaml | 27 +++-- install/manifests.yaml | 12 +- install/webhooks/certificate.yaml | 21 ---- install/webhooks/deployment.yaml | 38 ------- install/webhooks/kustomization.yaml | 104 ------------------ .../patches/webhook_in_authconfigs.yaml | 19 ---- install/webhooks/rbac.yaml | 58 ---------- install/webhooks/service.yaml | 11 -- 9 files changed, 38 insertions(+), 285 deletions(-) delete mode 100644 install/webhooks/certificate.yaml delete mode 100644 install/webhooks/deployment.yaml delete mode 100644 install/webhooks/kustomization.yaml delete mode 100644 install/webhooks/patches/webhook_in_authconfigs.yaml delete mode 100644 install/webhooks/rbac.yaml delete mode 100644 install/webhooks/service.yaml diff --git a/Makefile b/Makefile index 2288bc98..e9d7a948 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ endif PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) export PATH := $(PROJECT_DIR)/bin:$(PATH) -# Authorino manifests bundle (CRDs, RBAC, Webhook service) +# Authorino manifests bundle (CRDs, RBAC) AUTHORINO_MANIFESTS ?= $(PROJECT_DIR)/install/manifests.yaml # The Kubernetes namespace where to deploy the Authorino instance @@ -110,6 +110,7 @@ generate: vendor controller-gen ## Generates types deepcopy code manifests: controller-gen kustomize ## Generates the manifests in $PROJECT_DIR/install controller-gen crd:crdVersions=v1 rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=install/crd output:rbac:artifacts:config=install/rbac && $(KUSTOMIZE) build install > $(AUTHORINO_MANIFESTS) + $(MAKE) patch-webhook run: generate manifests ## Runs the application against the Kubernetes cluster configured in ~/.kube/config go run -ldflags "-X main.version=$(VERSION)" ./main.go server @@ -188,7 +189,7 @@ limitador: ## Deploys Limitador from kuadrant/authorino-examples into the Kubern ##@ Installation -.PHONY: install-operator uninstall-operator install-webhooks uninstall-webhooks install uninstall +.PHONY: install-operator uninstall-operator install uninstall patch-webhook AUTHORINO_OPERATOR_NAMESPACE ?= authorino-operator @@ -199,31 +200,25 @@ OPERATOR_BRANCH = $(OPERATOR_VERSION) endif install-operator: ## Installs Authorino Operator and corresponding version of the manifests into the Kubernetes cluster configured in ~/.kube/config kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/$(OPERATOR_BRANCH)/config/deploy/manifests.yaml + kubectl patch deployment/authorino-webhooks -n $(AUTHORINO_OPERATOR_NAMESPACE) -p '{"spec":{"template":{"spec":{"containers":[{"name":"webhooks","image":"$(AUTHORINO_IMAGE)","imagePullPolicy":"IfNotPresent"}]}}}}' kubectl -n $(AUTHORINO_OPERATOR_NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all uninstall-operator: ## Uninstalls Authorino Operator and corresponding version of the manifests from the Kubernetes cluster configured in ~/.kube/config kubectl delete -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/$(OPERATOR_BRANCH)/config/deploy/manifests.yaml -WEBHOOK_SERVICE_NAMESPACE ?= $(AUTHORINO_OPERATOR_NAMESPACE) - -install-webhooks: ## Creates the Authorino webhook service - cd install/webhooks && $(KUSTOMIZE) edit set namespace ${WEBHOOK_SERVICE_NAMESPACE} - $(KUSTOMIZE) build install/webhooks | kubectl -n $(WEBHOOK_SERVICE_NAMESPACE) apply -f - - # rollback kustomize edit - cd install/webhooks && $(KUSTOMIZE) edit set namespace authorino-webhooks - -uninstall-webhooks: ## Uninstalls the Authorino webhook service - cd install/webhooks && $(KUSTOMIZE) edit set namespace ${WEBHOOK_SERVICE_NAMESPACE} - $(KUSTOMIZE) build install/webhooks | kubectl -n $(WEBHOOK_SERVICE_NAMESPACE) delete -f - - # rollback kustomize edit - cd install/webhooks && $(KUSTOMIZE) edit set namespace authorino-webhooks - -install: manifests ## Installs the current manifests (CRD, RBAC, Webhook service) into the Kubernetes cluster configured in ~/.kube/config +install: manifests ## Installs the current manifests (CRD, RBAC) into the Kubernetes cluster configured in ~/.kube/config kubectl apply -f $(AUTHORINO_MANIFESTS) -uninstall: manifests ## Uninstalls the current manifests (CRD, RBAC, Webhook service) from the Kubernetes cluster configured in ~/.kube/config +uninstall: manifests ## Uninstalls the current manifests (CRD, RBAC) from the Kubernetes cluster configured in ~/.kube/config kubectl delete -f $(AUTHORINO_MANIFESTS) +patch-webhook: export WEBHOOK_NAMESPACE=$(AUTHORINO_OPERATOR_NAMESPACE) +patch-webhook: + envsubst \ + < $(AUTHORINO_MANIFESTS) \ + > $(AUTHORINO_MANIFESTS).tmp && \ + mv $(AUTHORINO_MANIFESTS).tmp $(AUTHORINO_MANIFESTS) + ##@ Deployment .PHONY: namespace certs deploy @@ -266,7 +261,7 @@ cluster: kind ## Starts a local Kubernetes cluster using Kind local-build: kind docker-build ## Builds an image based on the current branch and pushes it to the registry into the local Kubernetes cluster started with Kind $(KIND) load docker-image $(AUTHORINO_IMAGE) --name $(KIND_CLUSTER_NAME) -local-setup: cluster local-build cert-manager install-operator install install-webhooks namespace deploy user-apps ## Sets up a test/dev local Kubernetes server using Kind, loaded up with a freshly built Authorino image and apps +local-setup: cluster local-build cert-manager install-operator install namespace deploy user-apps ## Sets up a test/dev local Kubernetes server using Kind, loaded up with a freshly built Authorino image and apps kubectl -n $(NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all @{ \ echo "Now you can export the envoy service by doing:"; \ diff --git a/install/crd/patches/webhook_in_authconfigs.yaml b/install/crd/patches/webhook_in_authconfigs.yaml index 13d34bf4..d4fc0792 100644 --- a/install/crd/patches/webhook_in_authconfigs.yaml +++ b/install/crd/patches/webhook_in_authconfigs.yaml @@ -1,21 +1,20 @@ # The following patch applies the default strategy to convert between versions of the CRD -# Modify this file to patch the CRD for calling a proper webhook service. E.g.: -# -# spec: -# conversion: -# strategy: Webhook -# webhook: -# clientConfig: -# service: -# namespace: WEBHOOK_SERVICE_NAMESPACE -# name: WEBHOOK_SERVICE_NAME -# path: /convert -# conversionReviewVersions: -# - v1 +# The conversion webhook service is managed by the Authorino Operator apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: authconfigs.authorino.kuadrant.io + annotations: + cert-manager.io/inject-ca-from: ${WEBHOOK_NAMESPACE}/authorino-webhook-server-cert spec: conversion: - strategy: None + strategy: Webhook + webhook: + clientConfig: + service: + namespace: ${WEBHOOK_NAMESPACE} + name: authorino-webhooks + path: /convert + conversionReviewVersions: + - v1beta1 + - v1beta2 diff --git a/install/manifests.yaml b/install/manifests.yaml index edec7fba..280882b7 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2,11 +2,21 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + cert-manager.io/inject-ca-from: authorino-operator/authorino-webhook-server-cert controller-gen.kubebuilder.io/version: v0.9.0 name: authconfigs.authorino.kuadrant.io spec: conversion: - strategy: None + strategy: Webhook + webhook: + clientConfig: + service: + name: authorino-webhooks + namespace: authorino-operator + path: /convert + conversionReviewVersions: + - v1beta1 + - v1beta2 group: authorino.kuadrant.io names: kind: AuthConfig diff --git a/install/webhooks/certificate.yaml b/install/webhooks/certificate.yaml deleted file mode 100644 index 7087226d..00000000 --- a/install/webhooks/certificate.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - namespace: system - name: webhooks-ca -spec: - selfSigned: {} ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - namespace: system - name: webhook-server-cert -spec: - dnsNames: - - SERVICE_NAME.SERVICE_NAMESPACE.svc - - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local - issuerRef: - kind: Issuer - name: authorino-webhooks-ca - secretName: authorino-webhook-server-cert diff --git a/install/webhooks/deployment.yaml b/install/webhooks/deployment.yaml deleted file mode 100644 index 70a16f1e..00000000 --- a/install/webhooks/deployment.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: system ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - namespace: system - name: webhooks -spec: - selector: {} - template: - spec: - containers: - - name: webhooks - image: authorino:local - imagePullPolicy: IfNotPresent - command: - - authorino - - webhooks - ports: - - name: webhooks - containerPort: 9443 - - name: metrics - containerPort: 8080 - - name: healthz - containerPort: 8081 - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true - resources: {} - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: authorino-webhook-server-cert diff --git a/install/webhooks/kustomization.yaml b/install/webhooks/kustomization.yaml deleted file mode 100644 index 5832780d..00000000 --- a/install/webhooks/kustomization.yaml +++ /dev/null @@ -1,104 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- certificate.yaml -- deployment.yaml -- service.yaml -- rbac.yaml -- ../crd - -patchesStrategicMerge: -- patches/webhook_in_authconfigs.yaml - -commonLabels: - app: authorino - authorino-component: authorino-webhooks - -namespace: authorino-webhooks - -namePrefix: authorino- - -replacements: -- source: - fieldPath: .metadata.namespace - group: cert-manager.io - kind: Certificate - name: authorino-webhook-server-cert - version: v1 - targets: - - fieldPaths: - - .metadata.annotations.[cert-manager.io/inject-ca-from] - options: - create: true - delimiter: / - select: - kind: CustomResourceDefinition -- source: - fieldPath: .metadata.name - group: cert-manager.io - kind: Certificate - name: authorino-webhook-server-cert - version: v1 - targets: - - fieldPaths: - - .metadata.annotations.[cert-manager.io/inject-ca-from] - options: - create: true - delimiter: / - index: 1 - select: - kind: CustomResourceDefinition -- source: - fieldPath: .metadata.name - kind: Service - name: authorino-webhooks - version: v1 - targets: - - fieldPaths: - - .spec.dnsNames.0 - - .spec.dnsNames.1 - options: - create: true - delimiter: . - select: - group: cert-manager.io - kind: Certificate - name: authorino-webhook-server-cert - version: v1 - - fieldPaths: - - .spec.conversion.webhook.clientConfig.service.name - options: - create: true - select: - kind: CustomResourceDefinition -- source: - fieldPath: .metadata.namespace - kind: Service - name: authorino-webhooks - version: v1 - targets: - - fieldPaths: - - .spec.dnsNames.0 - - .spec.dnsNames.1 - options: - create: true - delimiter: . - index: 1 - select: - group: cert-manager.io - kind: Certificate - name: authorino-webhook-server-cert - version: v1 - - fieldPaths: - - .spec.conversion.webhook.clientConfig.service.namespace - options: - create: true - select: - kind: CustomResourceDefinition - - fieldPaths: - - subjects.0.namespace - select: - group: rbac.authorization.k8s.io - kind: RoleBinding - name: authorino-webhooks-manager diff --git a/install/webhooks/patches/webhook_in_authconfigs.yaml b/install/webhooks/patches/webhook_in_authconfigs.yaml deleted file mode 100644 index b1c0a10b..00000000 --- a/install/webhooks/patches/webhook_in_authconfigs.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: authconfigs.authorino.kuadrant.io - annotations: - cert-manager.io/inject-ca-from: WEBHOOK_CERTIFICATE_NAMESPACE/WEBHOOK_CERTIFICATE_NAME -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: WEBHOOK_SERVICE_NAMESPACE - name: WEBHOOK_SERVICE_NAME - path: /convert - conversionReviewVersions: - - v1beta1 - - v1beta2 diff --git a/install/webhooks/rbac.yaml b/install/webhooks/rbac.yaml deleted file mode 100644 index 54343754..00000000 --- a/install/webhooks/rbac.yaml +++ /dev/null @@ -1,58 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: webhooks-manager-role -rules: -- apiGroups: - - authorino.kuadrant.io - resources: - - authconfigs - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - authorino.kuadrant.io - resources: - - authconfigs/status - verbs: - - get - - patch - - update -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - create - - get - - list - - update -- apiGroups: - - "" - resources: - - configmaps - - events - verbs: - - create - - get - - list - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - namespace: system - name: webhooks-manager -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: authorino-webhooks-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: SERVICE_NAMESPACE diff --git a/install/webhooks/service.yaml b/install/webhooks/service.yaml deleted file mode 100644 index 974a7898..00000000 --- a/install/webhooks/service.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - namespace: system - name: webhooks -spec: - ports: - - port: 443 - protocol: TCP - targetPort: 9443 - selector: {} From 9a216b014cb49261d426f344d8caafd5d44e627d Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 29 Aug 2023 13:10:45 +0200 Subject: [PATCH 05/35] fix: unexport spicedb conversion functions --- api/v1beta2/auth_config_conversion.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go index efe0d208..5e4ddeed 100644 --- a/api/v1beta2/auth_config_conversion.go +++ b/api/v1beta2/auth_config_conversion.go @@ -693,8 +693,8 @@ func convertAuthorizationTo(name string, src AuthorizationSpec) *v1beta1.Authori Endpoint: src.SpiceDB.Endpoint, Insecure: src.SpiceDB.Insecure, SharedSecret: convertSecretKeyReferenceTo(src.SpiceDB.SharedSecret), - Subject: SpiceDBObjectTo(src.SpiceDB.Subject), - Resource: SpiceDBObjectTo(src.SpiceDB.Resource), + Subject: spiceDBObjectTo(src.SpiceDB.Subject), + Resource: spiceDBObjectTo(src.SpiceDB.Resource), Permission: convertValueOrSelectorTo(src.SpiceDB.Permission), } } @@ -734,8 +734,8 @@ func convertAuthorizationFrom(src *v1beta1.Authorization) (string, Authorization Endpoint: src.Authzed.Endpoint, Insecure: src.Authzed.Insecure, SharedSecret: convertSecretKeyReferenceFrom(src.Authzed.SharedSecret), - Subject: SpiceDBObjectFrom(src.Authzed.Subject), - Resource: SpiceDBObjectFrom(src.Authzed.Resource), + Subject: spiceDBObjectFrom(src.Authzed.Subject), + Resource: spiceDBObjectFrom(src.Authzed.Resource), Permission: convertValueOrSelectorFrom(src.Authzed.Permission), } } @@ -797,7 +797,7 @@ func convertKubernetesSubjectAccessReviewResourceAttributesFrom(src *v1beta1.Aut } } -func SpiceDBObjectTo(src *SpiceDBObject) *v1beta1.AuthzedObject { +func spiceDBObjectTo(src *SpiceDBObject) *v1beta1.AuthzedObject { if src == nil { return nil } @@ -807,7 +807,7 @@ func SpiceDBObjectTo(src *SpiceDBObject) *v1beta1.AuthzedObject { } } -func SpiceDBObjectFrom(src *v1beta1.AuthzedObject) *SpiceDBObject { +func spiceDBObjectFrom(src *v1beta1.AuthzedObject) *SpiceDBObject { if src == nil { return nil } From 86455df32ec22531d26c3c55596eb9896170df9a Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 29 Aug 2023 14:21:22 +0200 Subject: [PATCH 06/35] add tests for the conversion + fix conversion of a couple nil arrays/maps --- api/v1beta2/auth_config_conversion.go | 46 +- api/v1beta2/auth_config_conversion_test.go | 969 +++++++++++++++++++++ go.mod | 2 +- pkg/utils/utils.go | 3 + 4 files changed, 1004 insertions(+), 16 deletions(-) create mode 100644 api/v1beta2/auth_config_conversion_test.go diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go index 5e4ddeed..e353a9ec 100644 --- a/api/v1beta2/auth_config_conversion.go +++ b/api/v1beta2/auth_config_conversion.go @@ -285,6 +285,9 @@ func convertPtrValueOrSelectorFrom(src *v1beta1.StaticOrDynamicValue) *ValueOrSe } func convertNamedValuesOrSelectorsTo(src NamedValuesOrSelectors) (jsonProperties []v1beta1.JsonProperty) { + if src == nil { + return nil + } for name, valueOrSelector := range src { jsonProperties = append(jsonProperties, v1beta1.JsonProperty{ Name: name, @@ -296,6 +299,9 @@ func convertNamedValuesOrSelectorsTo(src NamedValuesOrSelectors) (jsonProperties } func convertNamedValuesOrSelectorsFrom(src []v1beta1.JsonProperty) NamedValuesOrSelectors { + if src == nil { + return nil + } namedValuesOrSelectors := NamedValuesOrSelectors{} for _, jsonProperty := range src { namedValuesOrSelectors[jsonProperty.Name] = ValueOrSelector{ @@ -423,6 +429,16 @@ func convertAuthenticationTo(name string, src AuthenticationSpec) *v1beta1.Ident } func convertAuthenticationFrom(src *v1beta1.Identity) (string, AuthenticationSpec) { + authentication := AuthenticationSpec{ + CommonEvaluatorSpec: CommonEvaluatorSpec{ + Priority: src.Priority, + Metrics: src.Metrics, + Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), + Cache: convertEvaluatorCachingFrom(src.Cache), + }, + Credentials: convertCredentialsFrom(src.Credentials), + } + var overrides []v1beta1.JsonProperty for _, extendedProperty := range src.ExtendedProperties { if !extendedProperty.Overwrite { @@ -430,6 +446,9 @@ func convertAuthenticationFrom(src *v1beta1.Identity) (string, AuthenticationSpe } overrides = append(overrides, extendedProperty.JsonProperty) } + if len(overrides) > 0 { + authentication.Overrides = ExtendedProperties(convertNamedValuesOrSelectorsFrom(overrides)) + } var defaults []v1beta1.JsonProperty for _, extendedProperty := range src.ExtendedProperties { @@ -438,17 +457,8 @@ func convertAuthenticationFrom(src *v1beta1.Identity) (string, AuthenticationSpe } defaults = append(defaults, extendedProperty.JsonProperty) } - - authentication := AuthenticationSpec{ - CommonEvaluatorSpec: CommonEvaluatorSpec{ - Priority: src.Priority, - Metrics: src.Metrics, - Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), - Cache: convertEvaluatorCachingFrom(src.Cache), - }, - Credentials: convertCredentialsFrom(src.Credentials), - Overrides: ExtendedProperties(convertNamedValuesOrSelectorsFrom(overrides)), - Defaults: ExtendedProperties(convertNamedValuesOrSelectorsFrom(defaults)), + if len(defaults) > 0 { + authentication.Defaults = ExtendedProperties(convertNamedValuesOrSelectorsFrom(defaults)) } switch src.GetType() { @@ -1000,8 +1010,11 @@ func convertStatusFrom(src v1beta1.AuthConfigStatus) AuthConfigStatus { } func convertStatusSummaryTo(src AuthConfigStatusSummary) v1beta1.Summary { - hostsReady := make([]string, len(src.HostsReady)) - copy(hostsReady, src.HostsReady) + var hostsReady []string + if len(src.HostsReady) > 0 { + hostsReady = make([]string, len(src.HostsReady)) + copy(hostsReady, src.HostsReady) + } return v1beta1.Summary{ Ready: src.Ready, @@ -1016,8 +1029,11 @@ func convertStatusSummaryTo(src AuthConfigStatusSummary) v1beta1.Summary { } func convertStatusSummaryFrom(src v1beta1.Summary) AuthConfigStatusSummary { - hostsReady := make([]string, len(src.HostsReady)) - copy(hostsReady, src.HostsReady) + var hostsReady []string + if len(src.HostsReady) > 0 { + hostsReady = make([]string, len(src.HostsReady)) + copy(hostsReady, src.HostsReady) + } return AuthConfigStatusSummary{ Ready: src.Ready, diff --git a/api/v1beta2/auth_config_conversion_test.go b/api/v1beta2/auth_config_conversion_test.go new file mode 100644 index 00000000..6376a9af --- /dev/null +++ b/api/v1beta2/auth_config_conversion_test.go @@ -0,0 +1,969 @@ +package v1beta2 + +import ( + "encoding/json" + "reflect" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/kuadrant/authorino/api/v1beta1" +) + +func TestConvertTo(t *testing.T) { + converted := &v1beta1.AuthConfig{} + authConfig().ConvertTo(converted) + + sort.Slice(converted.Spec.Identity, func(i, j int) bool { + return converted.Spec.Identity[i].Name < converted.Spec.Identity[j].Name + }) + sort.Slice(converted.Spec.Metadata, func(i, j int) bool { + return converted.Spec.Metadata[i].Name < converted.Spec.Metadata[j].Name + }) + sort.Slice(converted.Spec.Authorization, func(i, j int) bool { + return converted.Spec.Authorization[i].Name < converted.Spec.Authorization[j].Name + }) + sort.Slice(converted.Spec.Response, func(i, j int) bool { + return converted.Spec.Response[i].Name < converted.Spec.Response[j].Name + }) + for idx := range converted.Spec.Response { + if converted.Spec.Response[idx].Wristband != nil { + sort.Slice(converted.Spec.Response[idx].Wristband.CustomClaims, func(i, j int) bool { + return converted.Spec.Response[idx].Wristband.CustomClaims[i].Name < converted.Spec.Response[idx].Wristband.CustomClaims[j].Name + }) + } + if converted.Spec.Response[idx].JSON != nil { + sort.Slice(converted.Spec.Response[idx].JSON.Properties, func(i, j int) bool { + return converted.Spec.Response[idx].JSON.Properties[i].Name < converted.Spec.Response[idx].JSON.Properties[j].Name + }) + } + } + sort.Slice(converted.Spec.Callbacks, func(i, j int) bool { + return converted.Spec.Callbacks[i].Name < converted.Spec.Callbacks[j].Name + }) + + expected := hubAuthConfig() + if !reflect.DeepEqual(expected, converted) { + t.Error(cmp.Diff(expected, converted)) + } +} + +func TestConvertFrom(t *testing.T) { + converted := &AuthConfig{} + converted.ConvertFrom(hubAuthConfig()) + expected := authConfig() + if !reflect.DeepEqual(expected, converted) { + t.Error(cmp.Diff(expected, converted)) + } +} + +func authConfig() *AuthConfig { + authConfig := &AuthConfig{} + _ = json.Unmarshal([]byte(` + { + "metadata": { + "name": "auth-config" + }, + "spec": { + "authentication": { + "anonymousAccess": { + "anonymous": {}, + "credentials": { + "authorizationHeader": {} + }, + "priority": 1 + }, + "apiKeyUsers": { + "apiKey": { + "selector": { + "matchLabels": { + "app": "talker-api", + "talker-api/credential-kind": "api-key" + } + } + }, + "credentials": { + "authorizationHeader": { + "prefix": "API-KEY" + } + }, + "overrides": { + "groups": { + "value": [ + "admin" + ] + } + } + }, + "fromEnvoy": { + "credentials": { + "authorizationHeader": {} + }, + "plain": { + "selector": "context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt" + }, + "when": [ + { + "operator": "neq", + "selector": "context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.jwt_authn" + } + ] + }, + "k8sServiceAccountTokens": { + "credentials": { + "authorizationHeader": {} + }, + "kubernetesTokenReview": { + "audiences": [ + "talker-api.default.svc.cluster.local" + ] + } + }, + "mtlsUsers": { + "credentials": { + "authorizationHeader": {} + }, + "x509": { + "selector": { + "matchLabels": { + "app": "talker-api", + "talker-api/credential-kind": "ca-cert" + } + } + } + }, + "oauth2OpaqueTokens": { + "credentials": { + "authorizationHeader": {} + }, + "oauth2Introspection": { + "credentialsRef": { + "name": "oauth2-introspection-credentials" + }, + "endpoint": "https://accounts.company.com/oauth2/v1/introspect" + }, + "overrides": { + "jwtRBAC": { + "value": true + } + } + }, + "oidcServerUsers": { + "credentials": { + "authorizationHeader": {} + }, + "defaults": { + "username": { + "selector": "auth.identity.preferred_username" + } + }, + "jwt": { + "issuerUrl": "https://accounts.company.com", + "ttl": 3600 + }, + "overrides": { + "jwtRBAC": { + "value": true + } + } + } + }, + "authorization": { + "deny20percent": { + "opa": { + "rego": "allow { rand.intn(\"foo\", 100) < 80 }" + }, + "priority": 1 + }, + "externalOpaPolicy": { + "opa": { + "externalPolicy": { + "credentials": { + "authorizationHeader": {} + }, + "ttl": 3600, + "url": "https://raw.githubusercontent.com/repo/authorino-opa/main/allowed-methods.rego" + } + } + }, + "externalSpicedbPolicy": { + "spicedb": { + "endpoint": "spicedb.spicedb.svc.cluster.local:50051", + "insecure": true, + "permission": { + "selector": "context.request.http.method.@replace:{\"old\":\"GET\",\"new\":\"read\"}.@replace:{\"old\":\"POST\",\"new\":\"write\"}" + }, + "resource": { + "kind": { + "value": "blog/post" + }, + "name": { + "selector": "context.request.http.path.@extract:{\"sep\":\"/\",\"pos\":2}" + } + }, + "sharedSecretRef": { + "key": "grpc-preshared-key", + "name": "spicedb" + }, + "subject": { + "kind": { + "value": "blog/user" + }, + "name": { + "selector": "auth.identity.metadata.annotations.username" + } + } + } + }, + "inlineRego": { + "opa": { + "allValues": true, + "rego": "country = object.get(object.get(input.auth.metadata, \"geo-info\", {}), \"country_iso_code\", null)\nallow {\n allowed_countries := [\"ES\", \"FR\", \"IT\"]\n allowed_countries[_] == country\n}\n" + } + }, + "kubernetesRBAC": { + "kubernetesSubjectAccessReview": { + "user": { + "selector": "auth.identity.username" + } + }, + "when": [ + { + "patternRef": "admin-path" + }, + { + "operator": "eq", + "selector": "auth.identity.kubernetes-rbac", + "value": "true" + } + ] + }, + "simplePatternMatching": { + "patternMatching": { + "patterns": [ + { + "operator": "incl", + "selector": "auth.identity.roles", + "value": "admin" + } + ] + }, + "when": [ + { + "patternRef": "admin-path" + }, + { + "operator": "eq", + "selector": "auth.identity.jwtRBAC", + "value": "true" + } + ] + }, + "timestamp": { + "opa": { + "allValues": true, + "rego": "now = time.now_ns() / 1000000000\nallow = true\n" + }, + "priority": 20 + } + }, + "callbacks": { + "telemetry": { + "http": { + "body": { + "selector": "\\{\"requestId\":context.request.http.id,\"username\":\"{auth.identity.username}\",\"authorizationResult\":{auth.authorization}\\}\n" + }, + "contentType": "application/x-www-form-urlencoded", + "credentials": { + "authorizationHeader": {} + }, + "method": "POST", + "oauth2": { + "cache": true, + "clientId": "talker-api", + "clientSecretRef": { + "key": "client-secret", + "name": "talker-api-telemetry-credentials" + }, + "tokenUrl": "https://accounts.company.com/oauth2/v1/token" + }, + "url": "http://telemetry.server" + } + } + }, + "hosts": [ + "talker-api.127.0.0.1.nip.io", + "talker-api.default.svc.cluster.local" + ], + "metadata": { + "geoInfo": { + "cache": { + "key": { + "selector": "context.request.http.headers.x-forwarded-for.@extract:{\"sep\":\",\"}" + }, + "ttl": 3600 + }, + "http": { + "contentType": "application/x-www-form-urlencoded", + "credentials": { + "authorizationHeader": {} + }, + "headers": { + "Accept": { + "value": "application/json" + } + }, + "method": "GET", + "sharedSecretRef": { + "key": "shared-secret", + "name": "ip-location" + }, + "url": "http://ip-location.authorino.svc.cluster.local:3000/{context.request.http.headers.x-forwarded-for.@extract:{\"sep\":\",\"}}" + }, + "metrics": true + }, + "oidcUserInfo": { + "userInfo": { + "identitySource": "oidcServerUsers" + } + }, + "umaResourceInfo": { + "cache": { + "key": { + "selector": "context.request.http.path" + }, + "ttl": 60 + }, + "uma": { + "credentialsRef": { + "name": "talker-api-uma-credentials" + }, + "endpoint": "http://keycloak.authorino.svc.cluster.local:8080/auth/realms/kuadrant" + }, + "when": [ + { + "patternRef": "resourcePath" + } + ] + } + }, + "patterns": { + "adminPath": [ + { + "operator": "matches", + "selector": "context.request.http.path", + "value": "^/admin(/.*)?$" + } + ], + "resourcePath": [ + { + "operator": "matches", + "selector": "context.request.http.path", + "value": "^/greetings/\\d+$" + } + ] + }, + "response": { + "success": { + "dynamicMetadata": { + "username": { + "plain": { + "selector": "auth.identity.username" + } + } + }, + "headers": { + "festivalWristband": { + "wristband": { + "customClaims": { + "scope": { + "selector": "context.request.http.method.@case:lower" + }, + "uri": { + "selector": "context.request.http.path" + }, + "username": { + "selector": "auth.identity.username" + } + }, + "issuer": "https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/wristband", + "signingKeyRefs": [ + { + "algorithm": "ES256", + "name": "wristband-signing-key" + } + ], + "tokenDuration": 300 + } + }, + "x-auth-data": { + "json": { + "properties": { + "geo": { + "selector": "auth.metadata.geoInfo" + }, + "timestamp": { + "selector": "auth.authorization.timestamp" + }, + "username": { + "selector": "auth.identity.username" + } + } + } + }, + "x-auth-service": { + "plain": { + "value": "Authorino" + } + } + } + }, + "unauthenticated": { + "message": { + "value": "Authentication failed" + } + }, + "unauthorized": { + "headers": { + "random": { + "selector": "auth.authorization.deny20percent" + } + }, + "message": { + "value": "Access denied" + } + } + }, + "when": [ + { + "operator": "neq", + "selector": "context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.skipper_lua_filter|skip", + "value": "true" + } + ] + } + }`), &authConfig) + return authConfig +} + +func hubAuthConfig() *v1beta1.AuthConfig { + authConfig := &v1beta1.AuthConfig{} + _ = json.Unmarshal([]byte(` + { + "metadata": { + "name": "auth-config" + }, + "spec": { + "authorization": [ + { + "metrics": false, + "name": "deny20percent", + "opa": { + "allValues": false, + "inlineRego": "allow { rand.intn(\"foo\", 100) < 80 }" + }, + "priority": 1 + }, + { + "metrics": false, + "name": "externalOpaPolicy", + "opa": { + "allValues": false, + "externalRegistry": { + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "endpoint": "https://raw.githubusercontent.com/repo/authorino-opa/main/allowed-methods.rego", + "ttl": 3600 + } + }, + "priority": 0 + }, + { + "authzed": { + "endpoint": "spicedb.spicedb.svc.cluster.local:50051", + "insecure": true, + "permission": { + "valueFrom": { + "authJSON": "context.request.http.method.@replace:{\"old\":\"GET\",\"new\":\"read\"}.@replace:{\"old\":\"POST\",\"new\":\"write\"}" + } + }, + "resource": { + "kind": { + "value": "\"blog/post\"", + "valueFrom": {} + }, + "name": { + "valueFrom": { + "authJSON": "context.request.http.path.@extract:{\"sep\":\"/\",\"pos\":2}" + } + } + }, + "sharedSecretRef": { + "key": "grpc-preshared-key", + "name": "spicedb" + }, + "subject": { + "kind": { + "value": "\"blog/user\"", + "valueFrom": {} + }, + "name": { + "valueFrom": { + "authJSON": "auth.identity.metadata.annotations.username" + } + } + } + }, + "metrics": false, + "name": "externalSpicedbPolicy", + "priority": 0 + }, + { + "metrics": false, + "name": "inlineRego", + "opa": { + "allValues": true, + "inlineRego": "country = object.get(object.get(input.auth.metadata, \"geo-info\", {}), \"country_iso_code\", null)\nallow {\n allowed_countries := [\"ES\", \"FR\", \"IT\"]\n allowed_countries[_] == country\n}\n" + }, + "priority": 0 + }, + { + "kubernetes": { + "user": { + "valueFrom": { + "authJSON": "auth.identity.username" + } + } + }, + "metrics": false, + "name": "kubernetesRBAC", + "priority": 0, + "when": [ + { + "patternRef": "admin-path" + }, + { + "operator": "eq", + "selector": "auth.identity.kubernetes-rbac", + "value": "true" + } + ] + }, + { + "json": { + "rules": [ + { + "operator": "incl", + "selector": "auth.identity.roles", + "value": "admin" + } + ] + }, + "metrics": false, + "name": "simplePatternMatching", + "priority": 0, + "when": [ + { + "patternRef": "admin-path" + }, + { + "operator": "eq", + "selector": "auth.identity.jwtRBAC", + "value": "true" + } + ] + }, + { + "metrics": false, + "name": "timestamp", + "opa": { + "allValues": true, + "inlineRego": "now = time.now_ns() / 1000000000\nallow = true\n" + }, + "priority": 20 + } + ], + "callbacks": [ + { + "http": { + "body": { + "valueFrom": { + "authJSON": "\\{\"requestId\":context.request.http.id,\"username\":\"{auth.identity.username}\",\"authorizationResult\":{auth.authorization}\\}\n" + } + }, + "contentType": "application/x-www-form-urlencoded", + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "endpoint": "http://telemetry.server", + "method": "POST", + "oauth2": { + "cache": true, + "clientId": "talker-api", + "clientSecretRef": { + "key": "client-secret", + "name": "talker-api-telemetry-credentials" + }, + "tokenUrl": "https://accounts.company.com/oauth2/v1/token" + } + }, + "metrics": false, + "name": "telemetry", + "priority": 0 + } + ], + "denyWith": { + "unauthenticated": { + "message": { + "value": "\"Authentication failed\"", + "valueFrom": {} + } + }, + "unauthorized": { + "headers": [ + { + "name": "random", + "valueFrom": { + "authJSON": "auth.authorization.deny20percent" + } + } + ], + "message": { + "value": "\"Access denied\"", + "valueFrom": {} + } + } + }, + "hosts": [ + "talker-api.127.0.0.1.nip.io", + "talker-api.default.svc.cluster.local" + ], + "identity": [ + { + "anonymous": {}, + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "metrics": false, + "name": "anonymousAccess", + "priority": 1 + }, + { + "apiKey": { + "allNamespaces": false, + "selector": { + "matchLabels": { + "app": "talker-api", + "talker-api/credential-kind": "api-key" + } + } + }, + "credentials": { + "in": "authorization_header", + "keySelector": "API-KEY" + }, + "extendedProperties": [ + { + "name": "groups", + "overwrite": true, + "value": [ + "admin" + ], + "valueFrom": {} + } + ], + "metrics": false, + "name": "apiKeyUsers", + "priority": 0 + }, + { + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "metrics": false, + "name": "fromEnvoy", + "plain": { + "authJSON": "context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt" + }, + "priority": 0, + "when": [ + { + "operator": "neq", + "selector": "context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.jwt_authn" + } + ] + }, + { + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "kubernetes": { + "audiences": [ + "talker-api.default.svc.cluster.local" + ] + }, + "metrics": false, + "name": "k8sServiceAccountTokens", + "priority": 0 + }, + { + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "metrics": false, + "mtls": { + "allNamespaces": false, + "selector": { + "matchLabels": { + "app": "talker-api", + "talker-api/credential-kind": "ca-cert" + } + } + }, + "name": "mtlsUsers", + "priority": 0 + }, + { + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "extendedProperties": [ + { + "name": "jwtRBAC", + "overwrite": true, + "value": true, + "valueFrom": {} + } + ], + "metrics": false, + "name": "oauth2OpaqueTokens", + "oauth2": { + "credentialsRef": { + "name": "oauth2-introspection-credentials" + }, + "tokenIntrospectionUrl": "https://accounts.company.com/oauth2/v1/introspect" + }, + "priority": 0 + }, + { + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "extendedProperties": [ + { + "name": "jwtRBAC", + "overwrite": true, + "value": true, + "valueFrom": {} + }, + { + "name": "username", + "overwrite": false, + "valueFrom": { + "authJSON": "auth.identity.preferred_username" + } + } + ], + "metrics": false, + "name": "oidcServerUsers", + "oidc": { + "endpoint": "https://accounts.company.com", + "ttl": 3600 + }, + "priority": 0 + } + ], + "metadata": [ + { + "cache": { + "key": { + "valueFrom": { + "authJSON": "context.request.http.headers.x-forwarded-for.@extract:{\"sep\":\",\"}" + } + }, + "ttl": 3600 + }, + "http": { + "contentType": "application/x-www-form-urlencoded", + "credentials": { + "in": "authorization_header", + "keySelector": "" + }, + "endpoint": "http://ip-location.authorino.svc.cluster.local:3000/{context.request.http.headers.x-forwarded-for.@extract:{\"sep\":\",\"}}", + "headers": [ + { + "name": "Accept", + "value": "application/json", + "valueFrom": {} + } + ], + "method": "GET", + "sharedSecretRef": { + "key": "shared-secret", + "name": "ip-location" + } + }, + "metrics": true, + "name": "geoInfo", + "priority": 0 + }, + { + "metrics": false, + "name": "oidcUserInfo", + "priority": 0, + "userInfo": { + "identitySource": "oidcServerUsers" + } + }, + { + "cache": { + "key": { + "valueFrom": { + "authJSON": "context.request.http.path" + } + }, + "ttl": 60 + }, + "metrics": false, + "name": "umaResourceInfo", + "priority": 0, + "uma": { + "credentialsRef": { + "name": "talker-api-uma-credentials" + }, + "endpoint": "http://keycloak.authorino.svc.cluster.local:8080/auth/realms/kuadrant" + }, + "when": [ + { + "patternRef": "resourcePath" + } + ] + } + ], + "patterns": { + "adminPath": [ + { + "operator": "matches", + "selector": "context.request.http.path", + "value": "^/admin(/.*)?$" + } + ], + "resourcePath": [ + { + "operator": "matches", + "selector": "context.request.http.path", + "value": "^/greetings/\\d+$" + } + ] + }, + "response": [ + { + "metrics": false, + "name": "festivalWristband", + "priority": 0, + "wrapper": "httpHeader", + "wrapperKey": "festivalWristband", + "wristband": { + "customClaims": [ + { + "name": "scope", + "valueFrom": { + "authJSON": "context.request.http.method.@case:lower" + } + }, + { + "name": "uri", + "valueFrom": { + "authJSON": "context.request.http.path" + } + }, + { + "name": "username", + "valueFrom": { + "authJSON": "auth.identity.username" + } + } + ], + "issuer": "https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/wristband", + "signingKeyRefs": [ + { + "algorithm": "ES256", + "name": "wristband-signing-key" + } + ], + "tokenDuration": 300 + } + }, + { + "metrics": false, + "name": "username", + "plain": { + "valueFrom": { + "authJSON": "auth.identity.username" + } + }, + "priority": 0, + "wrapper": "envoyDynamicMetadata", + "wrapperKey": "username" + }, + { + "json": { + "properties": [ + { + "name": "geo", + "valueFrom": { + "authJSON": "auth.metadata.geoInfo" + } + }, + { + "name": "timestamp", + "valueFrom": { + "authJSON": "auth.authorization.timestamp" + } + }, + { + "name": "username", + "valueFrom": { + "authJSON": "auth.identity.username" + } + } + ] + }, + "metrics": false, + "name": "x-auth-data", + "priority": 0, + "wrapper": "httpHeader", + "wrapperKey": "x-auth-data" + }, + { + "metrics": false, + "name": "x-auth-service", + "plain": { + "value": "\"Authorino\"", + "valueFrom": {} + }, + "priority": 0, + "wrapper": "httpHeader", + "wrapperKey": "x-auth-service" + } + ], + "when": [ + { + "operator": "neq", + "selector": "context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.skipper_lua_filter|skip", + "value": "true" + } + ] + } + }`), &authConfig) + return authConfig +} diff --git a/go.mod b/go.mod index 4b146505..e0fa5c6b 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.5.9 github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 github.com/googleapis/gnostic v0.5.5 // indirect diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index fd5b8109..eb1167e3 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -42,6 +42,9 @@ func SliceContains[T comparable](s []T, val T) bool { // Map applies the given mapper function to each element in the input slice and returns a new slice with the results. func Map[T, U any](slice []T, f func(T) U) []U { + if slice == nil { + return nil + } arr := make([]U, len(slice)) for i, e := range slice { arr[i] = f(e) From aca78c6d44a4c0fd6b18c0d069d62758ff222f46 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 29 Aug 2023 15:03:50 +0200 Subject: [PATCH 07/35] One-of constraints for fields of AuthConfig v1beta2 --- install/crd/patches/oneof_in_authconfigs.yaml | 175 ++++++++++++++++- install/manifests.yaml | 176 ++++++++++++++++++ 2 files changed, 350 insertions(+), 1 deletion(-) diff --git a/install/crd/patches/oneof_in_authconfigs.yaml b/install/crd/patches/oneof_in_authconfigs.yaml index 0e758374..f805659c 100644 --- a/install/crd/patches/oneof_in_authconfigs.yaml +++ b/install/crd/patches/oneof_in_authconfigs.yaml @@ -1,5 +1,6 @@ -# Enables oneOf validation for the identity/metadata/authorization modes +# Enables oneOf validation for the identity/authentication, metadata, authorization, and response fields. +# v1beta1 - op: add path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/identity/items/oneOf value: @@ -162,3 +163,175 @@ selector: {} value: {} required: [operator, selector] + +# v1beta2 +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/authentication/additionalProperties/oneOf + value: + - properties: + credentials: {} + oauth2Introspection: {} + required: [oauth2Introspection] + - properties: + credentials: {} + jwt: {} + required: [jwt] + - properties: + credentials: {} + apiKey: {} + required: [apiKey] + - properties: + credentials: {} + x509: {} + required: [x509] + - properties: + credentials: {} + kubernetesTokenReview: {} + required: [kubernetesTokenReview] + - properties: + credentials: {} + anonymous: {} + required: [anonymous] + - properties: + credentials: {} + plain: {} + required: [plain] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/metadata/additionalProperties/oneOf + value: + - properties: + userInfo: {} + required: [userInfo] + - properties: + uma: {} + required: [uma] + - properties: + http: {} + required: [http] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/authorization/additionalProperties/oneOf + value: + - properties: + opa: {} + required: [opa] + - properties: + patternMatching: {} + required: [patternMatching] + - properties: + kubernetesSubjectAccessReview: {} + required: [kubernetesSubjectAccessReview] + - properties: + spicedb: {} + required: [spicedb] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/response/properties/success/properties/headers/additionalProperties/oneOf + value: + - properties: + wristband: {} + required: [wristband] + - properties: + json: {} + required: [json] + - properties: + plain: {} + required: [plain] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/response/properties/success/properties/dynamicMetadata/additionalProperties/oneOf + value: + - properties: + wristband: {} + required: [wristband] + - properties: + json: {} + required: [json] + - properties: + plain: {} + required: [plain] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/authorization/additionalProperties/properties/patternMatching/properties/patterns/items/oneOf + value: + - properties: + patternRef: {} + required: [patternRef] + - properties: + operator: {} + selector: {} + value: {} + required: [operator, selector] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/when/items/oneOf + value: + - properties: + patternRef: {} + required: [patternRef] + - properties: + operator: {} + selector: {} + value: {} + required: [operator, selector] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/authentication/additionalProperties/properties/when/items/oneOf + value: + - properties: + patternRef: {} + required: [patternRef] + - properties: + operator: {} + selector: {} + value: {} + required: [operator, selector] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/metadata/additionalProperties/properties/when/items/oneOf + value: + - properties: + patternRef: {} + required: [patternRef] + - properties: + operator: {} + selector: {} + value: {} + required: [operator, selector] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/authorization/additionalProperties/properties/when/items/oneOf + value: + - properties: + patternRef: {} + required: [patternRef] + - properties: + operator: {} + selector: {} + value: {} + required: [operator, selector] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/response/properties/success/properties/headers/additionalProperties/properties/when/items/oneOf + value: + - properties: + patternRef: {} + required: [patternRef] + - properties: + operator: {} + selector: {} + value: {} + required: [operator, selector] + +- op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/response/properties/success/properties/dynamicMetadata/additionalProperties/properties/when/items/oneOf + value: + - properties: + patternRef: {} + required: [patternRef] + - properties: + operator: {} + selector: {} + value: {} + required: [operator, selector] diff --git a/install/manifests.yaml b/install/manifests.yaml index 280882b7..44d7d525 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2529,6 +2529,42 @@ spec: properties: authentication: additionalProperties: + oneOf: + - properties: + credentials: {} + oauth2Introspection: {} + required: + - oauth2Introspection + - properties: + credentials: {} + jwt: {} + required: + - jwt + - properties: + apiKey: {} + credentials: {} + required: + - apiKey + - properties: + credentials: {} + x509: {} + required: + - x509 + - properties: + credentials: {} + kubernetesTokenReview: {} + required: + - kubernetesTokenReview + - properties: + anonymous: {} + credentials: {} + required: + - anonymous + - properties: + credentials: {} + plain: {} + required: + - plain properties: anonymous: description: Anonymous access. @@ -2807,6 +2843,18 @@ spec: If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. items: + oneOf: + - properties: + patternRef: {} + required: + - patternRef + - properties: + operator: {} + selector: {} + value: {} + required: + - operator + - selector properties: operator: description: 'The binary operator to be applied to the @@ -2907,6 +2955,23 @@ spec: type: object authorization: additionalProperties: + oneOf: + - properties: + opa: {} + required: + - opa + - properties: + patternMatching: {} + required: + - patternMatching + - properties: + kubernetesSubjectAccessReview: {} + required: + - kubernetesSubjectAccessReview + - properties: + spicedb: {} + required: + - spicedb properties: cache: description: Caching options for the resolved object returned @@ -3330,6 +3395,18 @@ spec: properties: patterns: items: + oneOf: + - properties: + patternRef: {} + required: + - patternRef + - properties: + operator: {} + selector: {} + value: {} + required: + - operator + - selector properties: operator: description: 'The binary operator to be applied to @@ -3502,6 +3579,18 @@ spec: If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. items: + oneOf: + - properties: + patternRef: {} + required: + - patternRef + - properties: + operator: {} + selector: {} + value: {} + required: + - operator + - selector properties: operator: description: 'The binary operator to be applied to the @@ -3847,6 +3936,19 @@ spec: type: array metadata: additionalProperties: + oneOf: + - properties: + userInfo: {} + required: + - userInfo + - properties: + uma: {} + required: + - uma + - properties: + http: {} + required: + - http properties: cache: description: Caching options for the resolved object returned @@ -4140,6 +4242,18 @@ spec: If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. items: + oneOf: + - properties: + patternRef: {} + required: + - patternRef + - properties: + operator: {} + selector: {} + value: {} + required: + - operator + - selector properties: operator: description: 'The binary operator to be applied to the @@ -4223,6 +4337,19 @@ spec: dynamicMetadata: additionalProperties: description: Settings of the success custom response item. + oneOf: + - properties: + wristband: {} + required: + - wristband + - properties: + json: {} + required: + - json + - properties: + plain: {} + required: + - plain properties: cache: description: Caching options for the resolved object @@ -4322,6 +4449,18 @@ spec: for the config to be enforced; otherwise, the config will be skipped. items: + oneOf: + - properties: + patternRef: {} + required: + - patternRef + - properties: + operator: {} + selector: {} + value: {} + required: + - operator + - selector properties: operator: description: 'The binary operator to be applied @@ -4433,6 +4572,19 @@ spec: type: object headers: additionalProperties: + oneOf: + - properties: + wristband: {} + required: + - wristband + - properties: + json: {} + required: + - json + - properties: + plain: {} + required: + - plain properties: cache: description: Caching options for the resolved object @@ -4536,6 +4688,18 @@ spec: for the config to be enforced; otherwise, the config will be skipped. items: + oneOf: + - properties: + patternRef: {} + required: + - patternRef + - properties: + operator: {} + selector: {} + value: {} + required: + - operator + - selector properties: operator: description: 'The binary operator to be applied @@ -4791,6 +4955,18 @@ spec: otherwise, Authorino skips the AuthConfig and returns to the auth request with status OK. items: + oneOf: + - properties: + patternRef: {} + required: + - patternRef + - properties: + operator: {} + selector: {} + value: {} + required: + - operator + - selector properties: operator: description: 'The binary operator to be applied to the content From 0599684f0389741d2f88caae12c8b458a0a371a4 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Thu, 31 Aug 2023 10:21:38 +0200 Subject: [PATCH 08/35] Use Authorino Operator install script --- .github/workflows/integration-test.yaml | 2 +- Makefile | 16 ++++------------ docs/getting-started.md | 2 +- docs/user-guides/anonymous-access.md | 2 +- docs/user-guides/api-key-authentication.md | 2 +- ...cated-rate-limiting-envoy-dynamic-metadata.md | 2 +- docs/user-guides/authzed.md | 2 +- docs/user-guides/caching.md | 2 +- docs/user-guides/deny-with-redirect-to-login.md | 2 +- ...ntication-architecture-festival-wristbands.md | 2 +- .../user-guides/envoy-jwt-authn-and-authorino.md | 2 +- docs/user-guides/external-metadata.md | 2 +- docs/user-guides/hello-world.md | 2 +- docs/user-guides/http-basic-authentication.md | 2 +- docs/user-guides/injecting-data.md | 2 +- .../json-pattern-matching-authorization.md | 2 +- .../keycloak-authorization-services.md | 2 +- .../kubernetes-subjectaccessreview.md | 2 +- docs/user-guides/kubernetes-tokenreview.md | 2 +- docs/user-guides/mtls-authentication.md | 2 +- docs/user-guides/oauth2-token-introspection.md | 2 +- docs/user-guides/oidc-jwt-authentication.md | 2 +- docs/user-guides/oidc-rbac.md | 2 +- docs/user-guides/oidc-user-info.md | 2 +- docs/user-guides/opa-authorization.md | 2 +- docs/user-guides/passing-credentials.md | 2 +- .../resource-level-authorization-uma.md | 2 +- docs/user-guides/sharding.md | 2 +- docs/user-guides/token-normalization.md | 2 +- docs/user-guides/validating-webhook.md | 2 +- 30 files changed, 33 insertions(+), 41 deletions(-) diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 083738dc..68efc425 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -39,7 +39,7 @@ jobs: kubectl -n cert-manager wait --timeout=300s --for=condition=Available deployments --all - name: Install Authorino Operator run: | - kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml + curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s kubectl -n authorino-operator wait --timeout=300s --for=condition=Available deployments --all - name: Create the namespace run: | diff --git a/Makefile b/Makefile index e9d7a948..47ef9ed8 100644 --- a/Makefile +++ b/Makefile @@ -151,15 +151,7 @@ e2e: ## Runs the end-to-end tests on a local environment setup ##@ Apps -.PHONY: cert-manager user-apps keycloak dex limitador - -cert-manager: ## Installs CertManager into the Kubernetes cluster configured in ~/.kube/config -ifeq (true,$(TLS_ENABLED)) - kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml - kubectl delete mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook - kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io/cert-manager-webhook - kubectl -n cert-manager wait --timeout=300s --for=condition=Available deployments --all -endif +.PHONY: user-apps keycloak dex limitador DEPLOY_KEYCLOAK ?= $(DEPLOY_IDPS) DEPLOY_DEX ?= $(DEPLOY_IDPS) @@ -198,8 +190,8 @@ OPERATOR_BRANCH = main else OPERATOR_BRANCH = $(OPERATOR_VERSION) endif -install-operator: ## Installs Authorino Operator and corresponding version of the manifests into the Kubernetes cluster configured in ~/.kube/config - kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/$(OPERATOR_BRANCH)/config/deploy/manifests.yaml +install-operator: ## Installs Authorino Operator and dependencies into the Kubernetes cluster configured in ~/.kube/config + curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/$(OPERATOR_BRANCH)/utils/install.sh | bash -s -- --git-ref $(OPERATOR_BRANCH) kubectl patch deployment/authorino-webhooks -n $(AUTHORINO_OPERATOR_NAMESPACE) -p '{"spec":{"template":{"spec":{"containers":[{"name":"webhooks","image":"$(AUTHORINO_IMAGE)","imagePullPolicy":"IfNotPresent"}]}}}}' kubectl -n $(AUTHORINO_OPERATOR_NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all @@ -261,7 +253,7 @@ cluster: kind ## Starts a local Kubernetes cluster using Kind local-build: kind docker-build ## Builds an image based on the current branch and pushes it to the registry into the local Kubernetes cluster started with Kind $(KIND) load docker-image $(AUTHORINO_IMAGE) --name $(KIND_CLUSTER_NAME) -local-setup: cluster local-build cert-manager install-operator install namespace deploy user-apps ## Sets up a test/dev local Kubernetes server using Kind, loaded up with a freshly built Authorino image and apps +local-setup: cluster local-build install-operator install namespace deploy user-apps ## Sets up a test/dev local Kubernetes server using Kind, loaded up with a freshly built Authorino image and apps kubectl -n $(NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all @{ \ echo "Now you can export the envoy service by doing:"; \ diff --git a/docs/getting-started.md b/docs/getting-started.md index 2acffc18..31e44189 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -51,7 +51,7 @@ Check out the [Feature specification](./features.md) page for more feature-speci The simplest way to install the Authorino Operator is by applying the manifest bundle: ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` The above will install the latest build of the Authorino Operator and latest version of the manifests (CRDs and RBAC), which by default points as well to the latest build of Authorino, both based on the `main` branches of each component. To install a stable released version of the Operator and therefore also defaults to its latest compatible stable release of Authorino, replace `main` with another tag of a proper release of the Operator, e.g. 'v0.2.0'. diff --git a/docs/user-guides/anonymous-access.md b/docs/user-guides/anonymous-access.md index 208d8f5e..65c7dff9 100644 --- a/docs/user-guides/anonymous-access.md +++ b/docs/user-guides/anonymous-access.md @@ -28,7 +28,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/api-key-authentication.md b/docs/user-guides/api-key-authentication.md index 9947409d..46e4553d 100644 --- a/docs/user-guides/api-key-authentication.md +++ b/docs/user-guides/api-key-authentication.md @@ -32,7 +32,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md b/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md index e5fd6168..f4e2625d 100644 --- a/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md +++ b/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md @@ -34,7 +34,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/authzed.md b/docs/user-guides/authzed.md index 9f985490..eeaa181e 100644 --- a/docs/user-guides/authzed.md +++ b/docs/user-guides/authzed.md @@ -27,7 +27,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/caching.md b/docs/user-guides/caching.md index f9dae05d..49da6e24 100644 --- a/docs/user-guides/caching.md +++ b/docs/user-guides/caching.md @@ -45,7 +45,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/deny-with-redirect-to-login.md b/docs/user-guides/deny-with-redirect-to-login.md index ab58220c..b00d38ad 100644 --- a/docs/user-guides/deny-with-redirect-to-login.md +++ b/docs/user-guides/deny-with-redirect-to-login.md @@ -34,7 +34,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Matrix Quotes web application diff --git a/docs/user-guides/edge-authentication-architecture-festival-wristbands.md b/docs/user-guides/edge-authentication-architecture-festival-wristbands.md index 2c3a3c96..8e5a54e9 100644 --- a/docs/user-guides/edge-authentication-architecture-festival-wristbands.md +++ b/docs/user-guides/edge-authentication-architecture-festival-wristbands.md @@ -59,7 +59,7 @@ kubectl -n keycloak port-forward deployment/keycloak 8080:8080 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Create the namespaces diff --git a/docs/user-guides/envoy-jwt-authn-and-authorino.md b/docs/user-guides/envoy-jwt-authn-and-authorino.md index 814c9954..bbf47d84 100644 --- a/docs/user-guides/envoy-jwt-authn-and-authorino.md +++ b/docs/user-guides/envoy-jwt-authn-and-authorino.md @@ -46,7 +46,7 @@ kubectl -n keycloak apply -f https://raw.githubusercontent.com/kuadrant/authorin ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/external-metadata.md b/docs/user-guides/external-metadata.md index b792ccf2..be020e31 100644 --- a/docs/user-guides/external-metadata.md +++ b/docs/user-guides/external-metadata.md @@ -36,7 +36,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/hello-world.md b/docs/user-guides/hello-world.md index ef202083..8d56a7b8 100644 --- a/docs/user-guides/hello-world.md +++ b/docs/user-guides/hello-world.md @@ -56,7 +56,7 @@ curl http://talker-api-authorino.127.0.0.1.nip.io:8000/hello -i ### Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ### Deploy Authorino diff --git a/docs/user-guides/http-basic-authentication.md b/docs/user-guides/http-basic-authentication.md index 0dc2dfe0..2b526f12 100644 --- a/docs/user-guides/http-basic-authentication.md +++ b/docs/user-guides/http-basic-authentication.md @@ -35,7 +35,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/injecting-data.md b/docs/user-guides/injecting-data.md index 935fdc1d..2ea557fa 100644 --- a/docs/user-guides/injecting-data.md +++ b/docs/user-guides/injecting-data.md @@ -33,7 +33,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/json-pattern-matching-authorization.md b/docs/user-guides/json-pattern-matching-authorization.md index 9d1ef955..5d893ee8 100644 --- a/docs/user-guides/json-pattern-matching-authorization.md +++ b/docs/user-guides/json-pattern-matching-authorization.md @@ -48,7 +48,7 @@ kubectl -n keycloak port-forward deployment/keycloak 8080:8080 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/keycloak-authorization-services.md b/docs/user-guides/keycloak-authorization-services.md index ca15e1a3..b13577fc 100644 --- a/docs/user-guides/keycloak-authorization-services.md +++ b/docs/user-guides/keycloak-authorization-services.md @@ -46,7 +46,7 @@ kubectl -n keycloak port-forward deployment/keycloak 8080:8080 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/kubernetes-subjectaccessreview.md b/docs/user-guides/kubernetes-subjectaccessreview.md index d801eca5..ebfe7279 100644 --- a/docs/user-guides/kubernetes-subjectaccessreview.md +++ b/docs/user-guides/kubernetes-subjectaccessreview.md @@ -35,7 +35,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/kubernetes-tokenreview.md b/docs/user-guides/kubernetes-tokenreview.md index 1712d382..b331106b 100644 --- a/docs/user-guides/kubernetes-tokenreview.md +++ b/docs/user-guides/kubernetes-tokenreview.md @@ -36,7 +36,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/mtls-authentication.md b/docs/user-guides/mtls-authentication.md index c32da662..df8b4f40 100644 --- a/docs/user-guides/mtls-authentication.md +++ b/docs/user-guides/mtls-authentication.md @@ -40,7 +40,7 @@ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4 ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy Authorino diff --git a/docs/user-guides/oauth2-token-introspection.md b/docs/user-guides/oauth2-token-introspection.md index 331e2699..0ede3a15 100644 --- a/docs/user-guides/oauth2-token-introspection.md +++ b/docs/user-guides/oauth2-token-introspection.md @@ -63,7 +63,7 @@ kubectl -n a12n-server port-forward deployment/a12n-server 8531:8531 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/oidc-jwt-authentication.md b/docs/user-guides/oidc-jwt-authentication.md index 45da10a5..c6e40261 100644 --- a/docs/user-guides/oidc-jwt-authentication.md +++ b/docs/user-guides/oidc-jwt-authentication.md @@ -47,7 +47,7 @@ kubectl -n keycloak port-forward deployment/keycloak 8080:8080 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/oidc-rbac.md b/docs/user-guides/oidc-rbac.md index 2d457030..58b23faa 100644 --- a/docs/user-guides/oidc-rbac.md +++ b/docs/user-guides/oidc-rbac.md @@ -48,7 +48,7 @@ kubectl -n keycloak port-forward deployment/keycloak 8080:8080 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/oidc-user-info.md b/docs/user-guides/oidc-user-info.md index 42b2eed2..5eea110f 100644 --- a/docs/user-guides/oidc-user-info.md +++ b/docs/user-guides/oidc-user-info.md @@ -49,7 +49,7 @@ kubectl -n keycloak port-forward deployment/keycloak 8080:8080 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/opa-authorization.md b/docs/user-guides/opa-authorization.md index 64e738aa..16d481d3 100644 --- a/docs/user-guides/opa-authorization.md +++ b/docs/user-guides/opa-authorization.md @@ -35,7 +35,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/passing-credentials.md b/docs/user-guides/passing-credentials.md index b9a27eeb..d9248482 100644 --- a/docs/user-guides/passing-credentials.md +++ b/docs/user-guides/passing-credentials.md @@ -33,7 +33,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/resource-level-authorization-uma.md b/docs/user-guides/resource-level-authorization-uma.md index fb94cebb..c57aacf4 100644 --- a/docs/user-guides/resource-level-authorization-uma.md +++ b/docs/user-guides/resource-level-authorization-uma.md @@ -47,7 +47,7 @@ kubectl -n keycloak port-forward deployment/keycloak 8080:8080 & ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/sharding.md b/docs/user-guides/sharding.md index 9a1d0b1f..9029fb39 100644 --- a/docs/user-guides/sharding.md +++ b/docs/user-guides/sharding.md @@ -35,7 +35,7 @@ kind create cluster --name authorino-tutorial ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy a couple instances of Authorino diff --git a/docs/user-guides/token-normalization.md b/docs/user-guides/token-normalization.md index 5d62e8ec..0e85bd14 100644 --- a/docs/user-guides/token-normalization.md +++ b/docs/user-guides/token-normalization.md @@ -48,7 +48,7 @@ kubectl -n keycloak apply -f https://raw.githubusercontent.com/kuadrant/authorin ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy the Talker API diff --git a/docs/user-guides/validating-webhook.md b/docs/user-guides/validating-webhook.md index 983283ed..46381e35 100644 --- a/docs/user-guides/validating-webhook.md +++ b/docs/user-guides/validating-webhook.md @@ -68,7 +68,7 @@ kubectl -n keycloak apply -f https://raw.githubusercontent.com/kuadrant/authorin ## 1. Install the Authorino Operator ```sh -kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml +curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` ## 2. Deploy Authorino From af96b5b6f62abc287d217e417b2c9544c333dc51 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Mon, 4 Sep 2023 15:48:24 +0200 Subject: [PATCH 09/35] fix: set status field hostReady in the conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This field is required and therefore should be set as the Kube API server would – i.e. not as nil, but with an empty array when it has no elements. --- api/v1beta2/auth_config_conversion.go | 14 +++------ api/v1beta2/auth_config_conversion_test.go | 34 ++++++++++++++++++++-- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go index e353a9ec..32e19ac6 100644 --- a/api/v1beta2/auth_config_conversion.go +++ b/api/v1beta2/auth_config_conversion.go @@ -1010,11 +1010,8 @@ func convertStatusFrom(src v1beta1.AuthConfigStatus) AuthConfigStatus { } func convertStatusSummaryTo(src AuthConfigStatusSummary) v1beta1.Summary { - var hostsReady []string - if len(src.HostsReady) > 0 { - hostsReady = make([]string, len(src.HostsReady)) - copy(hostsReady, src.HostsReady) - } + hostsReady := make([]string, len(src.HostsReady)) + copy(hostsReady, src.HostsReady) return v1beta1.Summary{ Ready: src.Ready, @@ -1029,11 +1026,8 @@ func convertStatusSummaryTo(src AuthConfigStatusSummary) v1beta1.Summary { } func convertStatusSummaryFrom(src v1beta1.Summary) AuthConfigStatusSummary { - var hostsReady []string - if len(src.HostsReady) > 0 { - hostsReady = make([]string, len(src.HostsReady)) - copy(hostsReady, src.HostsReady) - } + hostsReady := make([]string, len(src.HostsReady)) + copy(hostsReady, src.HostsReady) return AuthConfigStatusSummary{ Ready: src.Ready, diff --git a/api/v1beta2/auth_config_conversion_test.go b/api/v1beta2/auth_config_conversion_test.go index 6376a9af..359f5b77 100644 --- a/api/v1beta2/auth_config_conversion_test.go +++ b/api/v1beta2/auth_config_conversion_test.go @@ -59,7 +59,7 @@ func TestConvertFrom(t *testing.T) { func authConfig() *AuthConfig { authConfig := &AuthConfig{} - _ = json.Unmarshal([]byte(` + err := json.Unmarshal([]byte(` { "metadata": { "name": "auth-config" @@ -441,14 +441,29 @@ func authConfig() *AuthConfig { "value": "true" } ] + }, + "status": { + "summary": { + "ready": false, + "hostsReady": [], + "numHostsReady": "", + "numIdentitySources": 0, + "numMetadataSources": 0, + "numAuthorizationPolicies": 0, + "numResponseItems": 0, + "festivalWristbandEnabled": false + } } }`), &authConfig) + if err != nil { + panic(err) + } return authConfig } func hubAuthConfig() *v1beta1.AuthConfig { authConfig := &v1beta1.AuthConfig{} - _ = json.Unmarshal([]byte(` + err := json.Unmarshal([]byte(` { "metadata": { "name": "auth-config" @@ -963,7 +978,22 @@ func hubAuthConfig() *v1beta1.AuthConfig { "value": "true" } ] + }, + "status": { + "summary": { + "ready": false, + "hostsReady": [], + "numHostsReady": "", + "numIdentitySources": 0, + "numMetadataSources": 0, + "numAuthorizationPolicies": 0, + "numResponseItems": 0, + "festivalWristbandEnabled": false + } } }`), &authConfig) + if err != nil { + panic(err) + } return authConfig } From d968a6089f2bd5bd7a455886f7a45c4f31374070 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Mon, 4 Sep 2023 19:13:50 +0200 Subject: [PATCH 10/35] docs: updated README, architecture and features pages for the v1beta2 API --- README.md | 6 +- docs/architecture.md | 98 +++-- docs/features.md | 866 +++++++++++++++++++++---------------------- 3 files changed, 489 insertions(+), 481 deletions(-) diff --git a/README.md b/README.md index dc9a8ebc..28affd48 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ For a detailed description of the features above, refer to the [Features](./docs
Can't I just use Envoy JWT Authentication and RBAC filters? - Envoy's [JWT Authentication](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/jwt_authn/v3/config.proto.html) works pretty much similar to Authorino's [JOSE/JWT verification and validation for OpenID Connect](./docs/features.md#openid-connect-oidc-jwtjose-verification-and-validation-identityoidc). In both cases, the JSON Web Key Sets (JWKS) to verify the JWTs are auto-loaded and cached to be used in request-time. Moreover, you can configure for details such as where to extract the JWT from the HTTP request (header, param or cookie) and do some cool tricks regarding how dynamic metadata based on JWT claims can be injected to consecutive filters in the chain. + Envoy's [JWT Authentication](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/jwt_authn/v3/config.proto.html) works pretty much similar to Authorino's [JOSE/JWT verification and validation for OpenID Connect](./docs/features.md#jwt-verification-authenticationjwt). In both cases, the JSON Web Key Sets (JWKS) to verify the JWTs are auto-loaded and cached to be used in request-time. Moreover, you can configure for details such as where to extract the JWT from the HTTP request (header, param or cookie) and do some cool tricks regarding how dynamic metadata based on JWT claims can be injected to consecutive filters in the chain. However, in terms of authorization, while Envoy's implementation essentially allows to check for the list of audiences (`aud` JWT claim), Authorino opens up for a lot more options such as pattern-matching rules with operators and conditionals, built-in OPA and other methods of evaluating authorization policies. @@ -307,7 +307,7 @@ For a detailed description of the features above, refer to the [Features](./docs Authorino is an Envoy-compatible external authorization service. One can use Authorino with or without Istio. - In particular, [Istio Authorization Policies](https://istio.io/latest/docs/reference/config/security/authorization-policy/) can be seen, in terms of functionality and expressiveness, as a subset of one type of authorization policies supported by Authorino, the [JSON pattern-matching authorization](./docs/features.md#json-pattern-matching-authorization-rules-authorizationjson) policies. While Istio, however, is heavily focused on specific use cases of API Management, offering a relatively limited list of [supported attribute conditions](https://istio.io/latest/docs/reference/config/security/conditions/), Authorino is more generic, allowing to express authorization rules for a wider spectrum of use cases – ACLs, RBAC, ABAC, etc, pretty much counting on any attribute of the Envoy payload, identity object and external metadata available. + In particular, [Istio Authorization Policies](https://istio.io/latest/docs/reference/config/security/authorization-policy/) can be seen, in terms of functionality and expressiveness, as a subset of one type of authorization policies supported by Authorino, the [pattern-matching authorization](./docs/features.md#pattern-matching-authorization-authorizationpatternmatching) policies. While Istio, however, is heavily focused on specific use cases of API Management, offering a relatively limited list of [supported attribute conditions](https://istio.io/latest/docs/reference/config/security/conditions/), Authorino is more generic, allowing to express authorization rules for a wider spectrum of use cases – ACLs, RBAC, ABAC, etc, pretty much counting on any attribute of the Envoy payload, identity object and external metadata available. Authorino also provides built-in OPA authorization, several other methods of authentication and identity verification (e.g. Kubernetes token validation, API key-based authentication, OAuth token introspection, OIDC-discoverable JWT verification, etc), and features like fetching of external metadata (HTTP services, OIDC userinfo, UMA resource data), token normalization, wristband tokens and dynamic responses. These all can be used independently or combined, in a simple and straightforward Kubernetes-native fashion. @@ -327,7 +327,7 @@ For a detailed description of the features above, refer to the [Features](./docs No, you do not. However, if you are comfortable with [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) from Open Policy Agent (OPA), there are some quite interesting things you can do in Authorino, just as you would in any OPA server or OPA plugin, but leveraging Authorino's [built-in OPA module](./docs/features.md#open-policy-agent-opa-rego-policies-authorizationopa) instead. Authorino's OPA module is compiled as part of Authorino's code directly from the Golang packages, and imposes no extra latency to the evaluation of your authorization policies. Even the policies themselves are pre-compiled in reconciliation-time, for fast evaluation afterwards, in request-time. - On the other hand, if you do not want to learn Rego or in any case would like to combine it with declarative and Kubernetes-native authN/authZ spec for your services, Authorino does complement OPA with at least two other methods for expressing authorization policies – i.e. [JSON pattern-matching authorization rules](./docs/features.md#json-pattern-matching-authorization-rules-authorizationjson) and [Kubernetes SubjectAccessReview](./docs/features.md#kubernetes-subjectaccessreview-authorizationkubernetes), the latter allowing to rely completely on the Kubernetes RBAC. + On the other hand, if you do not want to learn Rego or in any case would like to combine it with declarative and Kubernetes-native authN/authZ spec for your services, Authorino does complement OPA with at least two other methods for expressing authorization policies – i.e. [pattern-matching authorization](./docs/features.md#pattern-matching-authorization-authorizationpatternmatching) and [Kubernetes SubjectAccessReview](./docs/features.md#kubernetes-subjectaccessreview-authorizationkubernetessubjectaccessreview), the latter allowing to rely completely on the Kubernetes RBAC. You break down, mix and combine these methods and technolgies in as many authorization policies as you want, potentially applying them according to specific conditions. Authorino will trigger the evaluation of concurrent policies in parallel, aborting the context if any of the processes denies access. diff --git a/docs/architecture.md b/docs/architecture.md index 4908f3c1..b7e508b8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,7 +41,7 @@ When Authorino is triggered by Envoy via the **gRPC** interface, it starts evalu Apart from static rules, these parameters can include instructions to contact online with external identity verifiers, external sources of **metadata** and policy decision points (**PDPs**). -On every request, Authorino's "working memory" is called [**Authorization JSON**](#the-authorization-json), a data structure that holds information about the context (the HTTP request) and objects from each phase of the auth pipeline: i.e., identity verification (phase i), ad-hoc metadata fetching (phase ii), authorization policy enforcement (phase iii), dynamic response (phase iv), and callbacks (phase v). The evaluators in each of these phases can both read and write from the Authorization JSON for dynamic steps and decisions of authN/authZ. +On every request, Authorino's "working memory" is called [**Authorization JSON**](#the-authorization-json), a data structure that holds information about the context (the HTTP request) and objects from each phase of the auth pipeline: i.e., authentication verification (phase i), ad-hoc metadata fetching (phase ii), authorization policy enforcement (phase iii), dynamic response (phase iv), and callbacks (phase v). The evaluators in each of these phases can both read and write from the Authorization JSON for dynamic steps and decisions of authN/authZ. ## Topologies @@ -58,7 +58,7 @@ Each topology above induces different measures for security. ![Centralized gateway topology](http://www.plantuml.com/plantuml/png/XOynJiKm343tdCBw0rk75aQ4_XyOsBY2rPWc-eaTEGa88Gu26pkduYJyfYf2KCJav5alJzddWbfg32OVFITKZ125PNGgaQ1e9MCIZaUS299Om3oF7fuCWD9OaAT0iFjuVO73xSrktcFolNdUqeP_j65RE_-blR_1DT_BOnDfFlrHlFYfNfvbvodOApZKuaGzor9VR-qXpuNq3iUJ06tj-nFieLjZou2kfcxvxmgiF713mnTIFxdIVI_iYMsDuHC0) -Recommended in the protected services to validate the origin of the traffic. It must have been proxied by Envoy. See Authorino [JSON injection](./features.md#json-injection-responsejson) for an extra validation option using a shared secret passed in HTTP header. +Recommended in the protected services to validate the origin of the traffic. It must have been proxied by Envoy. See Authorino [JSON injection](./features.md#json-injection-responsesuccessheadersdynamicmetadatajson) for an extra validation option using a shared secret passed in HTTP header. ### Centralized authorization service @@ -93,62 +93,82 @@ The desired protection for a service is declaratively stated by applying an `Aut An `AuthConfig` resource typically looks like the following: ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 +apiVersion: authorino.kuadrant.io/v1beta2 kind: AuthConfig metadata: name: my-api-protection spec: - # List of one or more hostname[:port] entries, lookup keys to find this config in request-time - # Authorino will try to prevent hostname collision by rejecting a hostname already taken. + # The list of public host names of the services protected by this AuthConfig resource. + # Authorino uses the host name provided in the payload of external authorization request to lookup for the corresponding AuthConfig to enforce. + # Hostname collisions are prevented by rejecting to index a hostname already taken by another AuthConfig. + # Format: hostname[:port] hosts: - - my-api.io # north-south traffic - - my-api.ns.svc.cluster.local # east-west traffic + - my-api.io:443 # north-south traffic + - my-api.ns.svc.cluster.local # east-west traffic + + # Set of stored named patterns to be reused in conditions and pattern-matching authorization rules + patterns: {"name" → {selector, operator, value}, …} + + # Top-level conditions for the AuthConfig to be enforced. + # If omitted, the AuthConfig will be enforced at all requests. + # If present, all conditions must match for the AuthConfig to be enforced; otherwise, Authorino skips the AuthConfig and returns to the auth request with status OK. + when: [{selector, operator, value | named pattern ref}, …] # List of one or more trusted sources of identity: - # - Endpoints of issuers of OpenId Connect ID tokens (JWTs) + # - Configurations to verify JSON Web Tokens (JWTs) issued by an OpenID Connect (OIDC) server # - Endpoints for OAuth 2.0 token introspection # - Attributes for the Kubernetes `TokenReview` API # - Label selectors for API keys (stored in Kubernetes `Secret`s) - # - mTLS trusted certificate issuers - # - HMAC secrets - identity: […] + # - Label selectors trusted x509 issuer certificates (stored in Kubernetes `Secret`s) + # - Selectors for plain identity objects supplied in the payload of the authorization request + # - Anonymous access configs + authentication: {"name" → {…}, …} # List of sources of external metadata for the authorization (optional): # - Endpoints for HTTP GET or GET-by-POST requests - # - OIDC UserInfo endpoints (associated with an OIDC token issuer) + # - OIDC UserInfo endpoints (associated with an OIDC token issuer specified in the authentication configs) # - User-Managed Access (UMA) resource registries - metadata: […] + metadata: {"name" → {…}, …} # List of authorization policies to be enforced (optional): - # - JSON pattern-matching rules (e.g. `context.request.http.path eq '/pets'`) + # - Pattern-matching rules (e.g. `context.request.http.path eq '/pets'`) # - Open Policy Agent (OPA) inline or external Rego policies # - Attributes for the Kubernetes `SubjectAccessReview` API - authorization: […] - - # List of dynamic response elements, to inject post-external authorization data into the request (optional): - # - JSON objects - # - Festival Wristbands (signed JWTs issued by Authorino) - # - Envoy Dynamic Metadata - response: […] - - # List of callback targets: - # - Endpoints for HTTP requests - callbacks: […] - -# Custom HTTP status code, message and headers to replace the default `401 Unauthorized` and `403 Forbidden` (optional) - denyWith: + # – Attributes for authorization with an external SpiceDB server + authorization: {"name" → {…}, …} + + # Customization to the response to the external authorization request (optional) + response: + # List of dynamic response elements into the request on success authoization (optional): + # - Plain text + # - JSON objects + # - Festival Wristbands (signed JWTs issued by Authorino) + success: + # List of HTTP headers to inject into the request post-authorization (optional): + headers: {"name" → {…}, …} + + # List of Envoy Dynamic Metadata to inject into the request post-authorization (optional): + dynamicMetadata: {"name" → {…}, …} + + # Custom HTTP status code, message and headers to replace the default `401 Unauthorized` response (optional) unauthenticated: code: 302 message: Redirecting to login headers: - - name: Location + "Location": value: https://my-app.io/login - unauthorized: {…} + + # Custom HTTP status code, message and headers to replace the default `and `403 Forbidden` response (optional) + unauthorized: {code, message, headers, body} + + # List of callback targets: + # - Endpoints for HTTP requests + callbacks: {"name" → {…}, …} ``` -Check out the [OAS](/install/crd/authorino.kuadrant.io_authconfigs.yaml) of the `AuthConfig` CRD for a formal specification of the options for `identity` verification, external `metadata` fetching, `authorization` policies, and dynamic `response`, as well as any other host protection capability implemented by Authorino. +Check out the [OAS](/install/crd/authorino.kuadrant.io_authconfigs.yaml) of the `AuthConfig` CRD for a formal specification of the options for `authentication` verification, external `metadata` fetching, `authorization` policies, and dynamic `response`, as well as any other host protection capability implemented by Authorino. -You can also read the specification from the CLI using the [`kubectl explain`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#explain) command. The Authorino CRD is required to have been installed in Kubernetes cluster. E.g. `kubectl explain authconfigs.spec.identity.extendedProperties`. +You can also read the specification from the CLI using the [`kubectl explain`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#explain) command. The Authorino CRD is required to have been installed in Kubernetes cluster. E.g. `kubectl explain authconfigs.spec.authentication.overrides`. A complete description of supported features and corresponding configuration options within an `AuthConfig` CR can be found in the [Features](./features.md) page. @@ -164,9 +184,9 @@ The above means that all replicas of an Authorino instance should be able to rec Among the multiple replicas of an instance, Authorino elects one replica to be leader. The leader is responsible for updating the status of reconciled `AuthConfig`s. If the leader eventually becomes unavailable, the instance will automatically elect another replica take its place as the new leader. -The status of an `AuthConfig` tells whether the resource is "ready" (i.e. indexed). It also includes summary information regarding the numbers of identity configs, metadata configs, authorization configs and response configs within the spec, as well as whether [Festival Wristband](./features.md#festival-wristband-tokens-responsewristband) tokens are being issued by the Authorino instance as by spec. +The status of an `AuthConfig` tells whether the resource is "ready" (i.e. indexed). It also includes summary information regarding the numbers of authentication configs, metadata configs, authorization configs and response configs within the spec, as well as whether [Festival Wristband](./features.md#festival-wristband-tokens-responsesuccessheadersdynamicmetadatawristband) tokens are being issued by the Authorino instance as by spec. -Apart from watching events related to `AuthConfig` custom resources, Authorino also watches events related to Kubernetes `Secret`s, as part of Authorino's [API key authentication](./features.md#api-key-identityapikey) feature. `Secret` resources that store API keys are linked to their corresponding `AuthConfig`s in the index. Whenever the Authorino instance detects a change in the set of API key `Secret`s linked to an `AuthConfig`s, the instance reconciles the index. +Apart from watching events related to `AuthConfig` custom resources, Authorino also watches events related to Kubernetes `Secret`s, as part of Authorino's [API key authentication](./features.md#api-key-authenticationapikey) feature. `Secret` resources that store API keys are linked to their corresponding `AuthConfig`s in the index. Whenever the Authorino instance detects a change in the set of API key `Secret`s linked to an `AuthConfig`s, the instance reconciles the index. Authorino only watches events related to `Secret`s whose `metadata.labels` match the label selector `--secret-label-selector` of the Authorino instance. The default values of the label selector for Kubernetes `Secret`s representing Authorino API keys is `authorino.kuadrant.io/managed-by=authorino`. @@ -176,13 +196,13 @@ Authorino only watches events related to `Secret`s whose `metadata.labels` match In each request to the protected API, Authorino triggers the so-called "Auth Pipeline", a set of configured *evaluators* that are organized in a 5-phase pipeline: -- **(i) Identity phase:** at least one source of identity (i.e., one identity evaluator) must resolve the supplied credential in the request into a valid identity or Authorino will otherwise reject the request as unauthenticated (401 HTTP response status). +- **(i) Authentication phase:** at least one source of identity (i.e., one authentication config) must resolve the supplied credential in the request into a valid identity or Authorino will otherwise reject the request as unauthenticated (401 HTTP response status). - **(ii) Metadata phase:** optional fetching of additional data from external sources, to add up to context and identity information, and used in authorization policies, dynamic responses and callback requests (phases iii to v). - **(iii) Authorization phase:** all unskipped policies must evaluate to a positive result ("authorized"), or Authorino will otherwise reject the request as unauthorized (403 HTTP response code). - **(iv) Response phase** – Authorino builds all user-defined response items (dynamic JSON objects and/or _Festival Wristband_ OIDC tokens), which are supplied back to the external authorization client within added HTTP headers or as Envoy Dynamic Metadata - **(v) Callbacks phase** – Authorino sends callbacks to specified HTTP endpoints. -Each phase is sequential to the other, from (i) to (v), while the evaluators within each phase are triggered concurrently or as prioritized. The **Identity** phase (i) is the only one required to list at least one evaluator (i.e. one identity source or more); **Metadata**, **Authorization** and **Response** phases can have any number of evaluators (including zero, and even be omitted in this case). +Each phase is sequential to the other, from (i) to (v), while the evaluators within each phase are triggered concurrently or as prioritized. The **Authentication** phase (i) is the only one required to list at least one evaluator (i.e. 1+ authentication configs); **Metadata**, **Authorization** and **Response** phases can have any number of evaluators (including zero, and even be omitted in this case). ## Host lookup @@ -283,9 +303,9 @@ After phase (iii), Authorino appends to the authorization JSON the results of th } ``` -[Festival Wristbands](./features.md#festival-wristband-tokens-responsewristband) and [Dynamic JSON](./features.md#json-injection-responsejson) responses can include dynamic values (custom claims/properties) fetched from the authorization JSON. These can be returned to the external authorization client in added HTTP headers or as Envoy [Well Known Dynamic Metadata](https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata). Check out [Dynamic response features](./features.md#dynamic-response-features-response) for details. +[Festival Wristbands](./features.md#festival-wristband-tokens-responsesuccessheadersdynamicmetadatawristband) and [Dynamic JSON](./features.md#json-injection-responsesuccessheadersdynamicmetadatajson) responses can include dynamic values (custom claims/properties) fetched from the authorization JSON. These can be returned to the external authorization client in added HTTP headers or as Envoy [Well Known Dynamic Metadata](https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata). Check out [Custom response features](./features.md#custom-response-features-response) for details. -For information about reading and fetching data from the Authorization JSON (syntax, functions, etc), check out [JSON paths](./features.md#common-feature-json-paths-valuefromauthjson). +For information about reading and fetching data from the Authorization JSON (syntax, functions, etc), check out [JSON paths](./features.md#common-feature-json-paths-selector). ## Raw HTTP Authorization interface @@ -302,7 +322,7 @@ In the raw HTTP interface, the host used to [lookup](#host-lookup) for an `AuthC OpenID Connect and User-Managed Access configurations, discovered usually at reconciliation-time from well-known discovery endpoints. -Cached individual OpenID Connect configurations discovered by Authorino can be configured to be auto-refreshed, by setting the corresponding `spec.identity.oidc.ttl` field in the AuthConfig (given in seconds, default: `0` – i.e. no cache update). +Cached individual OpenID Connect configurations discovered by Authorino can be configured to be auto-refreshed, by setting the corresponding `spec.authentication.jwt.ttl` field in the AuthConfig (given in seconds, default: `0` – i.e. no cache update). ### JSON Web Keys (JWKs) and JSON Web Key Sets (JWKS) diff --git a/docs/features.md b/docs/features.md index 482fb567..c19f3c0d 100644 --- a/docs/features.md +++ b/docs/features.md @@ -1,41 +1,39 @@ # Features - [Overview](#overview) -- [Common feature: JSON paths (`valueFrom.authJSON`)](#common-feature-json-paths-valuefromauthjson) +- [Common feature: JSON paths (`selector`)](#common-feature-json-paths-selector) - [Syntax](#syntax) - [String modifiers](#string-modifiers) - [Interpolation](#interpolation) -- [Identity verification & authentication features (`identity`)](#identity-verification--authentication-features-identity) - - [API key (`identity.apiKey`)](#api-key-identityapikey) - - [Kubernetes TokenReview (`identity.kubernetes`)](#kubernetes-tokenreview-identitykubernetes) - - [OpenID Connect (OIDC) JWT/JOSE verification and validation (`identity.oidc`)](#openid-connect-oidc-jwtjose-verification-and-validation-identityoidc) - - [OAuth 2.0 introspection (`identity.oauth2`)](#oauth-20-introspection-identityoauth2) - - [OpenShift OAuth (user-echo endpoint) (`identity.openshift`)](#openshift-oauth-user-echo-endpoint-identityopenshift) - - [Mutual Transport Layer Security (mTLS) authentication (`identity.mtls`)](#mutual-transport-layer-security-mtls-authentication-identitymtls) - - [Hash Message Authentication Code (HMAC) authentication (`identity.hmac`)](#hash-message-authentication-code-hmac-authentication-identityhmac) - - [Plain (`identity.plain`)](#plain-identityplain) - - [Anonymous access (`identity.anonymous`)](#anonymous-access-identityanonymous) +- [Identity verification \& authentication features (`authentication`)](#identity-verification--authentication-features-authentication) + - [API key (`authentication.apiKey`)](#api-key-authenticationapikey) + - [Kubernetes TokenReview (`authentication.kubernetesTokenReview`)](#kubernetes-tokenreview-authenticationkubernetestokenreview) + - [JWT verification (`authentication.jwt`)](#jwt-verification-authenticationjwt) + - [OAuth 2.0 introspection (`authentication.oauth2Introspection`)](#oauth-20-introspection-authenticationoauth2introspection) + - [X.509 client certificate authentication (`authentication.x509`)](#x509-client-certificate-authentication-authenticationx509) + - [Plain (`authentication.plain`)](#plain-authenticationplain) + - [Anonymous access (`authentication.anonymous`)](#anonymous-access-authenticationanonymous) - [Festival Wristband authentication](#festival-wristband-authentication) - - [_Extra:_ Auth credentials (`credentials`)](#extra-auth-credentials-credentials) - - [_Extra:_ Identity extension (`extendedProperties`)](#extra-identity-extension-extendedproperties) + - [_Extra:_ Auth credentials (`authentication.credentials`)](#extra-auth-credentials-authenticationcredentials) + - [_Extra:_ Identity extension (`authentication.defaults` and `authentication.overrides`)](#extra-identity-extension-authenticationdefaults-and-authenticationoverrides) - [External auth metadata features (`metadata`)](#external-auth-metadata-features-metadata) - [HTTP GET/GET-by-POST (`metadata.http`)](#http-getget-by-post-metadatahttp) - [OIDC UserInfo (`metadata.userInfo`)](#oidc-userinfo-metadatauserinfo) - [User-Managed Access (UMA) resource registry (`metadata.uma`)](#user-managed-access-uma-resource-registry-metadatauma) - [Authorization features (`authorization`)](#authorization-features-authorization) - - [JSON pattern-matching authorization rules (`authorization.json`)](#json-pattern-matching-authorization-rules-authorizationjson) + - [Pattern-matching authorization (`authorization.patternMatching`)](#pattern-matching-authorization-authorizationpatternmatching) - [Open Policy Agent (OPA) Rego policies (`authorization.opa`)](#open-policy-agent-opa-rego-policies-authorizationopa) - - [Kubernetes SubjectAccessReview (`authorization.kubernetes`)](#kubernetes-subjectaccessreview-authorizationkubernetes) - - [Authzed/SpiceDB (`authorization.authzed`)](#authzedspicedb-authorizationauthzed) - - [Keycloak Authorization Services (UMA-compliant Authorization API)](#keycloak-authorization-services-uma-compliant-authorization-api) -- [Dynamic response features (`response`)](#dynamic-response-features-response) - - [JSON injection (`response.json`)](#json-injection-responsejson) - - [Plain (`response.plain`)](#plain-responseplain) - - [Festival Wristband tokens (`response.wristband`)](#festival-wristband-tokens-responsewristband) - - [_Extra:_ Response wrappers (`wrapper` and `wrapperKey`)](#extra-response-wrappers-wrapper-and-wrapperkey) + - [Kubernetes SubjectAccessReview (`authorization.kubernetesSubjectAccessReview`)](#kubernetes-subjectaccessreview-authorizationkubernetessubjectaccessreview) + - [SpiceDB (`authorization.spicedb`)](#spicedb-authorizationspicedb) +- [Custom response features (`response`)](#custom-response-features-response) + - [Custom response forms: successful authorization vs custom denial status](#custom-response-forms-successful-authorization-vs-custom-denial-status) - [Added HTTP headers](#added-http-headers) - [Envoy Dynamic Metadata](#envoy-dynamic-metadata) - - [_Extra:_ Custom denial status (`denyWith`)](#extra-custom-denial-status-denywith) + - [Custom denial status (`response.unauthenticated` and `response.unauthorized`)](#custom-denial-status-responseunauthenticated-and-responseunauthorized) + - [Custom response methods](#custom-response-methods) + - [Plain text (`response.success..plain`)](#plain-text-responsesuccessheadersdynamicmetadataplain) + - [JSON injection (`response.success..json`)](#json-injection-responsesuccessheadersdynamicmetadatajson) + - [Festival Wristband tokens (`response.success..wristband`)](#festival-wristband-tokens-responsesuccessheadersdynamicmetadatawristband) - [Callbacks (`callbacks`)](#callbacks-callbacks) - [HTTP endpoints (`callbacks.http`)](#http-endpoints-callbackshttp) - [Common feature: Priorities](#common-feature-priorities) @@ -45,21 +43,21 @@ ## Overview -We call _features_ of Authorino the different things one can do to enforce identity verification & authentication and authorization on requests against protected services. These can be a specific identity verification method based on a supported authentication protocol, or a method to fetch additional auth metadata in request-time, etc. +We call _features_ of Authorino the different things one can do to enforce identity verification & authentication and authorization on requests to protected services. These can be a specific identity verification method based on a supported authentication protocol, or a method to fetch additional auth metadata in request-time, etc. -Most features of Authorino relate to the different phases of the [Auth Pipeline](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time) and therefore are configured in the Authorino [`AuthConfig`](./architecture.md#the-authorino-authconfig-custom-resource-definition-crd). An _identity verification feature_ usually refers to a functionality of Authorino such as the [API key-based authentication](#api-key-identityapikey) implemented by Authorino, the [validation of JWTs/OIDC ID tokens](#openid-connect-oidc-jwtjose-verification-and-validation-identityoidc), and authentication based on [Kubernetes TokenReviews](#kubernetes-tokenreview-identitykubernetes). Analogously, [OPA](#open-policy-agent-opa-rego-policies-authorizationopa), [JSON pattern-matching](#json-pattern-matching-authorization-rules-authorizationjson) and [Kubernetes SubjectAccessReview](#kubernetes-subjectaccessreview-authorizationkubernetes) are examples of _authorization features_ of Authorino. +Most features of Authorino relate to the different phases of the [Auth Pipeline](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time) and therefore are configured in the Authorino [`AuthConfig`](./architecture.md#the-authorino-authconfig-custom-resource-definition-crd). An _identity verification/authentication feature_ usually refers to a functionality of Authorino such as the [API key-based authentication](#api-key-authenticationapikey), the [validation of JWTs/OIDC ID tokens](#jwt-verification-authenticationjwt), and authentication based on [Kubernetes TokenReviews](#kubernetes-tokenreview-authenticationkubernetestokenreview). Analogously, [OPA](#open-policy-agent-opa-rego-policies-authorizationopa), [pattern-matching](#pattern-matching-authorization-authorizationpatternmatching) and [Kubernetes SubjectAccessReview](#kubernetes-subjectaccessreview-authorizationkubernetessubjectaccessreview) are examples of _authorization features_ of Authorino. -At a deeper level, a _feature_ can also be an additional functionality within a bigger feature, usually applicable to the whole class the bigger feature belongs to. For instance, the configuration of the location and key selector of [auth credentials](#extra-auth-credentials-credentials), available for all identity verification-related features. Other examples would be [_Identity extension_](#extra-identity-extension-extendedproperties) and [_Response wrappers_](#extra-response-wrappers-wrapper-and-wrapperkey). +At a deeper level, a _feature_ can also be an additional functionality within a bigger feature, usually applicable to the whole class the bigger feature belongs to. For instance, the configuration of how [auth credentials](#extra-auth-credentials-authenticationcredentials) expected to be carried in the request, which is broadly available for any identity verification method. Other examples are: [_Identity extension_](#extra-identity-extension-authenticationdefaults-and-authenticationoverrides) and [Priorities](#common-feature-priorities). A full specification of all features of Authorino that can be configured in an `AuthConfig` can be found in the official [spec](../install/crd/authorino.kuadrant.io_authconfigs.yaml) of the custom resource definition. -You can also learn about Authorino features by using the [`kubectl explain`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#explain) command in a Kubernetes cluster where the Authorino CRD has been installed. E.g. `kubectl explain authconfigs.spec.identity.extendedProperties`. +You can also learn about Authorino features by using the [`kubectl explain`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#explain) command in a Kubernetes cluster where the Authorino CRD has been installed. E.g. `kubectl explain authconfigs.spec.authentication.credentials`. -## Common feature: JSON paths ([`valueFrom.authJSON`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#ValueFromAuthJSON)) +## Common feature: JSON paths ([`selector`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#ValueOrSelector)) -The first feature of Authorino to learn about is a common functionality, used in the specification of many other features. _JSON paths_ have to do with reading data from the [Authorization JSON](./architecture.md#the-authorization-json), to refer to them in configuration of dynamic steps of API protection enforcing. +The first feature of Authorino to learn about is a common functionality used in the specification of many other features. _JSON paths_ are selectors of data from the [Authorization JSON](./architecture.md#the-authorization-json) used in parts of an AuthConfig for referring to dynamic values of each authorization request. -Usage examples of JSON paths are: dynamic URL and request parameters when fetching metadata from external sources, dynamic authorization policy rules, and dynamic authorization responses (injected JSON and Festival Wristband token claims). +Usage examples of JSON paths are: dynamic URLs and request parameters when fetching metadata from external sources, dynamic authorization policy rules, and dynamic authorization response attributes (e.g. injected HTTP headers, Festival Wristband token claims, etc). ### Syntax @@ -115,33 +113,33 @@ In combination with `@extract`, `@base64` can be used to extract the username in _JSON paths_ can be interpolated into strings to build template-like dynamic values. E.g. `"Hello, {auth.identity.name}!"`. -## Identity verification & authentication features ([`identity`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Identity)) +## Identity verification & authentication features ([`authentication`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#AuthenticationSpec)) -### API key ([`identity.apiKey`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Identity_APIKey)) +### API key ([`authentication.apiKey`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#ApiKeyAuthenticationSpec)) Authorino relies on Kubernetes `Secret` resources to represent API keys. To define an API key, create a `Secret` in the cluster containing an `api_key` entry that holds the value of the API key. -API key secrets must be created in the same namespace of the `AuthConfig` (default) or `spec.identity.apiKey.allNamespaces` must be set to `true` (only works with [cluster-wide Authorino instances](./architecture.md#cluster-wide-vs-namespaced-instances)). +API key secrets must be created in the same namespace of the `AuthConfig` (default) or `spec.authentication.apiKey.allNamespaces` must be set to `true` (only works with [cluster-wide Authorino instances](./architecture.md#cluster-wide-vs-namespaced-instances)). -API key secrets must be labeled with the labels that match the selectors specified in `spec.identity.apiKey.selector` in the `AuthConfig`. +API key secrets must be labeled with the labels that match the selectors specified in `spec.authentication.apiKey.selector` in the `AuthConfig`. -Whenever an `AuthConfig` is indexed, Authorino will also index all matching API key secrets. In order for Authorino to also watch events related to API key secrets individually (e.g. new `Secret` created, updates, deletion/revocation), `Secret`s must also include a label that matches Authorino's bootstrap configuration `--secret-label-selector` (default: `authorino.kuadrant.io/managed-by=authorino`). This label may or may not be present to `spec.identity.apiKey.selector` in the `AuthConfig` without implications for the caching of the API keys when triggered by the reconciliation of the `AuthConfig`; however, if not present, individual changes related to the API key secret (i.e. without touching the `AuthConfig`) will be ignored by the reconciler. +Whenever an `AuthConfig` is indexed, Authorino will also index all matching API key secrets. In order for Authorino to also watch events related to API key secrets individually (e.g. new `Secret` created, updates, deletion/revocation), `Secret`s must also include a label that matches Authorino's bootstrap configuration `--secret-label-selector` (default: `authorino.kuadrant.io/managed-by=authorino`). This label may or may not be present to `spec.authentication.apiKey.selector` in the `AuthConfig` without implications for the caching of the API keys when triggered by the reconciliation of the `AuthConfig`; however, if not present, individual changes related to the API key secret (i.e. without touching the `AuthConfig`) will be ignored by the reconciler. **Example.** For the following `AuthConfig`: ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 +apiVersion: authorino.kuadrant.io/v1beta2 kind: AuthConfig metadata: name: my-api-protection namespace: authorino-system spec: hosts: - - my-api.io - identity: - - name: api-key-users + - my-api.io + authentication: + "api-key-users": apiKey: selector: matchLabels: # the key-value set used to select the matching `Secret`s; resources including these labels will be accepted as valid API keys to authenticate to this service @@ -167,7 +165,7 @@ type: Opaque The resolved identity object, added to the authorization JSON following an API key identity source evaluation, is the Kubernetes `Secret` resource (as JSON). -### Kubernetes TokenReview ([`identity.kubernetes`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Identity_KubernetesAuth)) +### Kubernetes TokenReview ([`authentication.kubernetesTokenReview`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#KubernetesTokenReviewSpec)) Authorino can verify Kubernetes-valid access tokens (using Kubernetes [TokenReview](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1) API). @@ -178,39 +176,39 @@ The list of `audiences` of the token must include the requested host and port of For the following `AuthConfig` CR, the Kubernetes token must include the audience `my-api.io`: ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 +apiVersion: authorino.kuadrant.io/v1beta2 kind: AuthConfig metadata: name: my-api-protection spec: hosts: - - my-api.io - identity: - - name: cluster-users - kubernetes: {} + - my-api.io + authentication: + "cluster-users": + kubernetesTokenReview: {} ``` Whereas for the following `AuthConfig` CR, the Kubernetes token audiences must include **foo** and **bar**: ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 +apiVersion: authorino.kuadrant.io/v1beta2 kind: AuthConfig metadata: name: my-api-protection spec: hosts: - - my-api.io - identity: - - name: cluster-users - kubernetes: + - my-api.io + authentication: + "cluster-users": + kubernetesTokenReview: audiences: - - foo - - bar + - foo + - bar ``` The resolved identity object added to the authorization JSON following a successful Kubernetes authentication identity evaluation is the `status` field of TokenReview response (see [TokenReviewStatus](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/#TokenReviewStatus) for reference). -### OpenID Connect (OIDC) JWT/JOSE verification and validation ([`identity.oidc`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Identity_OidcConfig)) +### JWT verification ([`authentication.jwt`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#JwtAuthenticationSpec)) In reconciliation-time, using [OpenID Connect Discovery well-known endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig), Authorino automatically discovers and caches OpenID Connect configurations and associated JSON Web Key Sets (JWKS) for all OpenID Connect issuers declared in an `AuthConfig`. Then, in request-time, Authorino verifies the JSON Web Signature (JWS) and check the time validity of signed JSON Web Tokens (JWT) supplied on each request. @@ -222,11 +220,11 @@ The `kid` claim stated in the JWT header must match one of the keys cached by Au The decoded payload of the validated JWT is appended to the authorization JSON as the resolved identity. -OpenID Connect configurations and linked JSON Web Key Sets can be configured to be automatically refreshed (pull again from the OpenID Connect Discovery well-known endpoints), by setting the `identity.oidc.ttl` field (given in seconds, default: `0` – i.e. auto-refresh disabled). +OpenID Connect configurations and linked JSON Web Key Sets can be configured to be automatically refreshed (pull again from the OpenID Connect Discovery well-known endpoints), by setting the `authentication.jwt.ttl` field (given in seconds, default: `0` – i.e. auto-refresh disabled). For an excellent summary of the underlying concepts and standards that relate OpenID Connect and JSON Object Signing and Encryption (JOSE), see this [article](https://access.redhat.com/blogs/766093/posts/1976593) by Jan Rusnacko. For official specification and RFCs, see [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html), [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html), [JSON Web Token (JWT) (RFC7519)](https://datatracker.ietf.org/doc/html/rfc7519), and [JSON Object Signing and Encryption (JOSE)](http://www.iana.org/assignments/jose/jose.xhtml). -### OAuth 2.0 introspection ([`identity.oauth2`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Identity_OAuth2Config)) +### OAuth 2.0 introspection ([`authentication.oauth2Introspection`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#OAuth2TokenIntrospectionSpec)) For bare OAuth 2.0 implementations, Authorino can perform token introspection on the access tokens supplied in the requests to protected APIs. @@ -238,23 +236,13 @@ Developers must set the token introspection endpoint in the `AuthConfig`, as wel The response returned by the OAuth2 server to the token introspection request is the resolved identity appended to the authorization JSON. -### OpenShift OAuth (user-echo endpoint) (`identity.openshift`) - - - - - -
Not implemented - In analysis
- -Online token introspection of OpenShift-valid access tokens based on OpenShift's user-echo endpoint. +### X.509 client certificate authentication (`authentication.x509`) -### Mutual Transport Layer Security (mTLS) authentication (`identity.mtls`) - -Authorino can verify x509 certificates presented by clients for authentication on the request to the protected APIs, at application level. +Authorino can verify X.509 certificates presented by clients for authentication on the request to the protected APIs, at application level. Trusted root Certificate Authorities (CA) are stored in Kubernetes Secrets labeled according to selectors specified in the AuthConfig, watched and indexed by Authorino. Make sure to create proper `kubernetes.io/tls`-typed Kubernetes Secrets, containing the public certificates of the CA stored in either a `tls.crt` or `ca.crt` entry inside the secret. -Trusted root CA secrets must be created in the same namespace of the `AuthConfig` (default) or `spec.identity.mtls.allNamespaces` must be set to `true` (only works with [cluster-wide Authorino instances](./architecture.md#cluster-wide-vs-namespaced-instances)). +Trusted root CA secrets must be created in the same namespace of the `AuthConfig` (default) or `spec.authentication.x509.allNamespaces` must be set to `true` (only works with [cluster-wide Authorino instances](./architecture.md#cluster-wide-vs-namespaced-instances)). The identity object resolved out of a client x509 certificate is equal to the subject field of the certificate, and it serializes as JSON within the Authorization JSON usually as follows: @@ -284,21 +272,11 @@ The identity object resolved out of a client x509 certificate is equal to the su } ``` -### Hash Message Authentication Code (HMAC) authentication (`identity.hmac`) - - - - - -
Not implemented - Planned (#9)
- -Authentication based on the validation of a hash code generated from the contextual information of the request to the protected API, concatenated with a secret known by the API consumer. - -### Plain (`identity.plain`) +### Plain (`authentication.plain`) Authorino can read plain identity objects, based on authentication tokens provided and verified beforehand using other means (e.g. Envoy [JWT Authentication filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/jwt_authn_filter#config-http-filters-jwt-authn), Kubernetes API server authentication), and injected into the payload to the external authorization service. -The plain identity object is retrieved from the Authorization JSON based on a [JSON path](#common-feature-json-paths-valuefromauthjson) specified in the `AuthConfig`. +The plain identity object is retrieved from the Authorization JSON based on a [JSON path](#common-feature-json-paths-selector) specified in the `AuthConfig`. This feature is particularly useful in cases where authentication/identity verification is handled before invoking the authorization service and its resolved value injected in the payload can be trusted. Examples of applications for this feature include: - Authentication handled in Envoy leveraging the Envoy JWT Authentication filter (decoded JWT injected as 'metadata_context') @@ -308,15 +286,15 @@ Example of `AuthConfig` to retrieve plain identity object from the Authorization ```yaml spec: - identity: - - name: plain - plain: - authJSON: context.metadata_context.filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt + authentication: + "pre-validated-jwt": + plain: + selector: context.metadata_context.filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt ``` If the specified JSON path does not exist in the Authorization JSON or the value is `null`, the identity verification will fail and, unless other identity config succeeds, Authorino will halt the Auth Pipeline with the usual `401 Unauthorized`. -### Anonymous access (`identity.anonymous`) +### Anonymous access (`authentication.anonymous`) Literally a no-op evaluator for the identity verification phase that returns a static identity object `{"anonymous":true}`. @@ -328,45 +306,64 @@ Example of `AuthConfig` spec that falls back to anonymous access when OIDC authe ```yaml spec: - identity: - - name: jwt - oidc: { endpoint: ... } - - name: anonymous - priority: 1 # expired oidc token, missing creds, etc. default to anonymous access - anonymous: {} + authentication: + "jwt": + jwt: + issuerUrl: "…" + "anonymous": + priority: 1 # expired oidc token, missing creds, etc. default to anonymous access + anonymous: {} authorization: - - name: read-only-access-if-authn-fails - when: - - selector: auth.identity.anonymous - operator: eq - value: "true" - json: - rules: - - selector: context.request.http.method - operator: eq - value: GET + "read-only-access-if-authn-fails": + when: + - selector: auth.identity.anonymous + operator: eq + value: "true" + patternMatching: + patterns: + - selector: context.request.http.method + operator: eq + value: GET ``` ### Festival Wristband authentication -Authorino-issued [Festival Wristband](#festival-wristband-tokens-responsewristband) tokens can be validated as any other signed JWT using Authorino's [OpenID Connect (OIDC) JWT/JOSE verification and validation](#openid-connect-oidc-jwtjose-verification-and-validation-identityoidc). +Authorino-issued [Festival Wristband](#festival-wristband-tokens-responsesuccessheadersdynamicmetadatawristband) tokens can be validated as any other signed JWT using Authorino's [JWT verification](#jwt-verification-authenticationjwt). The value of the issuer must be the same issuer specified in the custom resource for the protected API originally issuing wristband. Eventually, this can be the same custom resource where the wristband is configured as a valid source of identity, but not necessarily. -### _Extra:_ Auth credentials ([`credentials`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Credentials)) +### _Extra:_ Auth credentials ([`authentication.credentials`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#Credentials)) All the identity verification methods supported by Authorino can be configured regarding the location where access tokens and credentials (i.e. authentication secrets) fly within the request. -By default, authentication secrets are expected to be supplied in the `Authorization` HTTP header, with the `Bearer` prefix and plain authentication secret, separated by space. The full list of supported options for the location of authentication secrets and selector is specified in the table below: +By default, authentication secrets are expected to be supplied in the `Authorization` HTTP header, with the default `Bearer` prefix and the plain authentication secret separated by space. -| Location (`credentials.in`) | Description | Selector (`credentials.keySelector`) | -|-----------------------------|-----------------------------|--------------------------------------------------| -| `authorization_header` | `Authorization` HTTP header | Prefix (default: `Bearer`) | -| `custom_header` | Custom HTTP header | Name of the header. Value should have no prefix. | -| `query` | Query string parameter | Name of the parameter | -| `cookie` | Cookie header | ID of the cookie entry | +The full list of supported options is exemplified below: -### _Extra:_ Identity extension ([`extendedProperties`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Identity)) +```yaml +spec: + authentication: + "creds-in-the-authz-header": + credentials: + authorizationHeader: + prefix: JWT + + "creds-in-a-custom-header": + credentials: + customHeader: + name: X-MY-CUSTOM-HEADER + prefix: "" + + "creds-in-a-query-param": + queryString: + name: my_param + + "creds-in-a-cookie-entry": + cookie: + name: cookie-key +``` + +### _Extra:_ Identity extension ([`authentication.defaults`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#ExtendedProperties) and [`authentication.overrides`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#ExtendedProperties)) Resolved identity objects can be extended with user-defined JSON properties. Values can be static or fetched from the Authorization JSON. @@ -376,9 +373,9 @@ In such cases, identity extension can be used to normalize the token to always i In case of extending an existing property of the identity object (replacing), the API allows to control whether to overwrite the value or not. This is particularly useful for normalizing tokens of a same identity source that nonetheless may occasionally differ in structure, such as in the case of JWT claims that sometimes may not be present but can be safely replaced with another (e.g. `username` or `sub`). -## External auth metadata features ([`metadata`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Metadata)) +## External auth metadata features ([`metadata`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#Metadata)) -### HTTP GET/GET-by-POST ([`metadata.http`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Metadata_GenericHTTP)) +### HTTP GET/GET-by-POST ([`metadata.http`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#HttpEndpointSpec)) Generic HTTP adapter that sends a request to an external service. It can be used to fetch external metadata for the authorization policies (phase ii of the Authorino [Auth Pipeline](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time)), or as a web hook. @@ -388,21 +385,21 @@ POST request parameters as well as the encoding of the content can be controlled Authentication of Authorino with the external metadata server can be set either via long-lived shared secret stored in a Kubernetes Secret or via OAuth2 client credentials grant. For long-lived shared secret, set the `sharedSecretRef` field. For OAuth2 client credentials grant, use the `oauth2` option. -In both cases, the location where the secret (long-lived or OAuth2 access token) travels in the request performed to the external HTTP service can be specified in the [`credentials`](#extra-auth-credentials-credentials) field. By default, the authentication secret is supplied in the `Authorization` header with the `Bearer` prefix. +In both cases, the location where the secret (long-lived or OAuth2 access token) travels in the request performed to the external HTTP service can be specified in the [`credentials`](#extra-auth-credentials-authenticationcredentials) field. By default, the authentication secret is supplied in the `Authorization` header with the `Bearer` prefix. Custom headers can be set with the `headers` field. Nevertheless, headers such as `Content-Type` and `Authorization` (or eventual custom header used for carrying the authentication secret, set instead via the `credentials` option) will be superseded by the respective values defined for the fields `contentType` and `sharedSecretRef`. -### OIDC UserInfo ([`metadata.userInfo`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Metadata_UserInfo)) +### OIDC UserInfo ([`metadata.userInfo`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#UserInfoMetadataSpec)) Online fetching of OpenID Connect (OIDC) UserInfo data (phase ii of the Authorino [Auth Pipeline](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time)), associated with an OIDC identity source configured and resolved in phase (i). Apart from possibly complementing information of the JWT, fetching OpenID Connect UserInfo in request-time can be particularly useful for remote checking the state of the session, as opposed to only verifying the JWT/JWS offline. -Implementation requires an OpenID Connect issuer ([`spec.identity.oidc`](#openid-connect-oidc-jwtjose-verification-and-validation-identityoidc)) configured in the same `AuthConfig`. +Implementation requires a JWT verification authentication config ([`spec.authentication.jwt`](#jwt-verification-authenticationjwt)) in the same `AuthConfig`, so the well-known configuration of the OpenId Connect (OIDC) issuer can be reused. The response returned by the OIDC server to the UserInfo request is appended (as JSON) to `auth.metadata` in the authorization JSON. -### User-Managed Access (UMA) resource registry ([`metadata.uma`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Metadata_UMA)) +### User-Managed Access (UMA) resource registry ([`metadata.uma`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#UmaMetadataSpec)) User-Managed Access (UMA) is an OAuth-based protocol for resource owners to allow other users to access their resources. Since the UMA-compliant server is expected to know about the resources, Authorino includes a client that fetches resource data from the server and adds that as metadata of the authorization payload. @@ -416,14 +413,14 @@ It's important to notice that Authorino does NOT manage resources in the UMA-com The resources data is added as metadata of the authorization payload and passed as input for the configured authorization policies. All resources returned by the UMA-compliant server in the query by URI are passed along. They are available in the PDPs (authorization payload) as `input.auth.metadata.custom-name => Array`. (See [The "Auth Pipeline"](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time) for details.) -## Authorization features ([`authorization`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Authorization)) +## Authorization features ([`authorization`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#Authorization)) -### JSON pattern-matching authorization rules ([`authorization.json`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Authorization_JSONPatternMatching)) +### Pattern-matching authorization ([`authorization.patternMatching`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#PatternMatchingAuthorizationSpec)) -Grant/deny access based on simple pattern-matching expressions ("rules") compared against values selected from the Authorization JSON. +Grant/deny access based on simple pattern-matching expressions ("patterns") compared against values selected from the Authorization JSON. Each expression is a tuple composed of: -- a `selector`, to fetch from the Authorization JSON – see [Common feature: JSON paths](#common-feature-json-paths-valuefromauthjson) for details about syntax; +- a `selector`, to fetch from the Authorization JSON – see [Common feature: JSON paths](#common-feature-json-paths-selector) for details about syntax; - an `operator` – `eq` (_equals_), `neq` (_not equal_); `incl` (_includes_) and `excl` (_excludes_), for arrays; and `matches`, for regular expressions; - a fixed comparable `value` @@ -432,22 +429,22 @@ Rules can mix and combine literal expressions and references to expression sets ```yaml spec: authorization: - - name: my-simple-json-pattern-matching-policy - json: - rules: # All rules must match for access to be granted - - selector: auth.identity.email_verified - operator: eq - value: "true" - - patternRef: admin + "my-simple-json-pattern-matching-policy": + patternMatching: + patterns: # All patterns must match for access to be granted + - selector: auth.identity.email_verified + operator: eq + value: "true" + - patternRef: admin patterns: admin: # a named pattern that can be reused in other sets of rules or conditions - - selector: auth.identity.roles - operator: incl - value: admin + - selector: auth.identity.roles + operator: incl + value: admin ``` -### Open Policy Agent (OPA) Rego policies ([`authorization.opa`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Authorization_OPA)) +### Open Policy Agent (OPA) Rego policies ([`authorization.opa`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#OpaAuthorizationSpec)) You can model authorization policies in [Rego language](https://www.openpolicyagent.org/docs/latest/policy-language/) and add them as part of the protection of your APIs. @@ -461,7 +458,7 @@ Authorino's built-in OPA module precompiles the policies during reconciliation o An optional field `allValues: boolean` makes the values of all rules declared in the Rego document to be returned in the OPA output after policy evaluation. When disabled (default), only the boolean value `allow` is returned. Values of internal rules of the Rego document can be referenced in subsequent policies/phases of the Auth Pipeline. -### Kubernetes SubjectAccessReview ([`authorization.kubernetes`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Authorization_KubernetesAuthz)) +### Kubernetes SubjectAccessReview ([`authorization.kubernetesSubjectAccessReview`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#KubernetesSubjectAccessReviewAuthorizationSpec)) Access control enforcement based on rules defined in the Kubernetes authorization system, i.e. `Role`, `ClusterRole`, `RoleBinding` and `ClusterRoleBinding` resources of Kubernetes RBAC. @@ -500,16 +497,15 @@ Kubernetes' authorization policy configs look like the following in an Authorino ```yaml authorization: - - name: kubernetes-rbac - kubernetes: - user: - valueFrom: # values of the parameter can be fixed (`value`) or fetched from the Authorization JSON (`valueFrom.authJSON`) - authJSON: auth.identity.metadata.annotations.userid + "kubernetes-rbac": + kubernetesSubjectAccessReview: + user: # values of the parameter can be fixed (`value`) or fetched from the Authorization JSON (`selector`) + selector: auth.identity.metadata.annotations.userid groups: [] # user groups to test for. # for resource attributes permission checks; omit it to perform a non-resource attributes SubjectAccessReview with path and method/verb assumed from the original request - # if included, use the resource attributes, where the values for each parameter can be fixed (`value`) or fetched from the Authorization JSON (`valueFrom.authJSON`) + # if included, use the resource attributes, where the values for each parameter can be fixed (`value`) or fetched from the Authorization JSON (`selector`) resourceAttributes: namespace: value: default @@ -518,117 +514,99 @@ authorization: resource: value: pets # the resource kind name: - valueFrom: { authJSON: context.request.http.path.@extract:{"sep":"/","pos":2} } # resource name – e.g., the {id} in `/pets/{id}` + selector: context.request.http.path.@extract:{"sep":"/","pos":2} # resource name – e.g., the {id} in `/pets/{id}` verb: - valueFrom: { authJSON: context.request.http.method.@case:lower } # api operation – e.g., copying from the context to use the same http method of the request + selector: context.request.http.method.@case:lower # api operation – e.g., copying from the context to use the same http method of the request ``` `user` and properties of `resourceAttributes` can be defined from fixed values or patterns of the Authorization JSON. An array of `groups` (optional) can as well be set. When defined, it will be used in the `SubjectAccessReview` request. -### Authzed/SpiceDB ([`authorization.authzed`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Authorization_Authzed)) +### SpiceDB ([`authorization.spicedb`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#SpiceDBAuthorizationSpec)) -Check permission requests sent to a Google Zanzibar-based [Authzed/SpiceDB](https://authzed.com) instance, via gRPC. +Check permission requests via gRPC with an external Google Zanzibar-inspired [SpiceDB](https://authzed.com) server, by Authzed. Subject, resource and permission parameters can be set to static values or read from the Authorization JSON. ```yaml spec: authorization: - - name: authzed - authzed: - endpoint: spicedb:50051 - insecure: true # disables TLS - sharedSecretRef: - name: spicedb - key: token - subject: - kind: - value: blog/user - name: - valueFrom: - authJSON: auth.identity.sub - resource: - kind: - value: blog/post - name: - valueFrom: - authJSON: context.request.http.path.@extract:{"sep":"/","pos":2} # /posts/{id} - permission: - valueFrom: - authJSON: context.request.http.method + "spicedb": + spicedb: + endpoint: spicedb:50051 + insecure: true # disables TLS + sharedSecretRef: + name: spicedb + key: token + subject: + kind: + value: blog/user + name: + selector: auth.identity.sub + resource: + kind: + value: blog/post + name: + selector: context.request.http.path.@extract:{"sep":"/","pos":2} # /posts/{id} + permission: + selector: context.request.http.method ``` -### Keycloak Authorization Services (UMA-compliant Authorization API) +## Custom response features ([`response`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#Response)) + +### Custom response forms: successful authorization vs custom denial status - - - - -
Not implemented - In analysis
+The response to the external authorization request can be customized in the following fashion: +- Successful authorization (`response.success`) + - Added HTTP headers (`response.success.headers`) + - Envoy Dynamic Metadata (`response.success.dynamicMetadata`) +- Custom denial status + - Unauthenticated (`response.unauthenticated`) + - Unauthorized (`response.unauthorized`) -Online delegation of authorization to a Keycloak server. +Successful authorization custom responses can be set based on any of the supported custom authorization methods: +- Plain text value +- JSON injection +- Festival Wristband Tokens + +#### Added HTTP headers -## Dynamic response features ([`response`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Response)) +Set custom responses as HTTP headers injected in the request post-successful authorization by specifying one of the supported methods under `response.success.headers`. -### JSON injection ([`response.json`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Response_DynamicJSON)) +#### Envoy Dynamic Metadata -User-defined dynamic JSON objects generated by Authorino in the response phase, from static or dynamic data of the auth pipeline, and passed back to the external authorization client within added HTTP headers or as Envoy [Well Known Dynamic Metadata](https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata). +Authorino custom response methods can also be used to propagate [Envoy Dynamic Metadata](https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata). To do so, set one of the supported methods under `response.success.dynamicMetadata`. -The following Authorino `AuthConfig` custom resource is an example that defines 3 dynamic JSON response items, where two items are returned to the client, stringified, in added HTTP headers, and the third is wrapped as Envoy Dynamic Metadata("emitted", in Envoy terminology). Envoy proxy can be configured to "pipe" dynamic metadata emitted by one filter into another filter – for example, from external authorization to rate limit. +A custom response exported as Envoy Dynamic Metadata can be configured in the Envoy route or virtual host configuration, to be passed. E.g., for reading metadata emitted by the authorization service in the following scheme: `{ "auth-data": { "api-key-ns": string, "api-key-name": string } }` for a rate limiting: ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 -kind: AuthConfig -metadata: - namespace: my-namespace - name: my-api-protection -spec: - hosts: - - my-api.io - identity: - - name: edge - apiKey: - selector: - matchLabels: - authorino.kuadrant.io/managed-by: authorino - credentials: - in: authorization_header - keySelector: APIKEY - response: - - name: a-json-returned-in-a-header - wrapper: httpHeader # can be omitted - wrapperKey: x-my-custom-header # if omitted, name of the header defaults to the name of the config ("a-json-returned-in-a-header") - json: - properties: - - name: prop1 - value: value1 - - name: prop2 - valueFrom: - authJSON: some.path.within.auth.json - - - name: another-json-returned-in-a-header - wrapperKey: x-ext-auth-other-json - json: - properties: - - name: propX - value: valueX - - - name: a-json-returned-as-envoy-metadata - wrapper: envoyDynamicMetadata - wrapperKey: auth-data - json: - properties: - - name: api-key-ns - valueFrom: - authJSON: auth.identity.metadata.namespace - - name: api-key-name - valueFrom: - authJSON: auth.identity.metadata.name +# Envoy config snippet to inject `user_namespace` and `username` rate limit descriptors from metadata returned by Authorino +rate_limits: +- actions: + - metadata: + metadata_key: + key: "envoy.filters.http.ext_authz" + path: + - key: auth-data + - key: api-key-ns + descriptor_key: user_namespace + - metadata: + metadata_key: + key: "envoy.filters.http.ext_authz" + path: + - key: auth-data + - key: api-key-name + descriptor_key: username ``` -### Plain ([`response.plain`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Response_Plain)) +#### Custom denial status ([`response.unauthenticated`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#DenyWithSpec) and [`response.unauthorized`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#DenyWithSpec)) + +By default, Authorino will inform Envoy to respond with `401 Unauthorized` or `403 Forbidden` respectively when the identity verification (phase i of the [Auth Pipeline](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time)) or authorization (phase ii) fail. These can be customized respectively by specifying `spec.response.unauthanticated` and `spec.response.unauthorized` in the `AuthConfig`. + +### Custom response methods + +#### Plain text ([`response.success..plain`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#PlainAuthResponseSpec)) Simpler, yet more generalized form, for extending the authorization response for header mutation and Envoy Dynamic Metadata, based on plain text values. @@ -636,62 +614,115 @@ The value can be static: ```yaml response: -- name: x-auth-service - plain: - value: Authorino + success: + headers: + "x-auth-service" + plain: + value: Authorino ``` or fetched dynamically from the [Authorization JSON](./architecture.md#the-authorization-json) (which includes support for [interpolation](#interpolation)): ```yaml -- name: x-username - plain: - valueFrom: - authJSON: auth.identity.username +response: + success: + headers: + "x-username": + plain: + selector: auth.identity.username ``` -### Festival Wristband tokens ([`response.wristband`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Response_Wristband)) +#### JSON injection ([`response.success..json`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#JsonAuthResponseSpec)) + +User-defined dynamic JSON objects generated by Authorino in the response phase, from static or dynamic data of the auth pipeline, and passed back to the external authorization client within added HTTP headers or Dynamic Metadata. + +The following Authorino `AuthConfig` custom resource is an example that defines 3 dynamic JSON response items, where two items are returned to the client, stringified, in added HTTP headers, and the third as Envoy Dynamic Metadata. Envoy proxy can be configured to propagate the dynamic metadata emitted by Authorino into another filter – e.g. the rate limit filter. + +```yaml +apiVersion: authorino.kuadrant.io/v1beta2 +kind: AuthConfig +metadata: + namespace: my-namespace + name: my-api-protection +spec: + hosts: + - my-api.io + authentication: + "edge": + apiKey: + selector: + matchLabels: + authorino.kuadrant.io/managed-by: authorino + credentials: + authorizationHeader: + prefix: APIKEY + response: + success: + headers: + "x-my-custom-header": + json: + properties: + "prop1": + value: value1 + "prop2": + selector: some.path.within.auth.json + "x-ext-auth-other-json": + json: + properties: + "propX": + value: valueX + + dynamicMetadata: + "auth-data": + json: + properties: + "api-key-ns": + seletor: auth.identity.metadata.namespace + "api-key-name": + selector: auth.identity.metadata.name +``` + +#### Festival Wristband tokens ([`response.success..wristband`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#WristbandAuthResponseSpec)) Festival Wristbands are signed OpenID Connect JSON Web Tokens (JWTs) issued by Authorino at the end of the auth pipeline and passed back to the client, typically in added HTTP response header. It is an opt-in feature that can be used to implement Edge Authentication Architecture (EAA) and enable token normalization. Authorino wristbands include minimal standard JWT claims such as `iss`, `iat`, and `exp`, and optional user-defined custom claims, whose values can be static or dynamically fetched from the authorization JSON. The Authorino `AuthConfig` custom resource below sets an API protection that issues a wristband after a successful authentication via API key. Apart from standard JWT claims, the wristband contains 2 custom claims: a static value `aud=internal` and a dynamic value `born` that fetches from the authorization JSON the date/time of creation of the secret that represents the API key used to authenticate. ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 +apiVersion: authorino.kuadrant.io/v1beta2 kind: AuthConfig metadata: namespace: my-namespace name: my-api-protection spec: hosts: - - my-api.io - identity: - - name: edge + - my-api.io + authentication: + "edge": apiKey: selector: matchLabels: authorino.kuadrant.io/managed-by: authorino credentials: - in: authorization_header - keySelector: APIKEY + authorizationHeader: + prefix: APIKEY response: - - name: my-wristband - wristband: - issuer: https://authorino-oidc.default.svc:8083/my-namespace/my-api-protection/my-wristband - customClaims: - - name: aud - value: internal - - name: born - valueFrom: - authJSON: auth.identity.metadata.creationTimestamp - tokenDuration: 300 - signingKeyRefs: - - name: my-signing-key - algorithm: ES256 - - name: my-old-signing-key - algorithm: RS256 - wrapper: httpHeader # can be omitted - wrapperKey: x-ext-auth-wristband # whatever http header name desired - defaults to the name of the response config ("my-wristband") + success: + headers: + "x-wristband": + wristband: + issuer: https://authorino-oidc.default.svc:8083/my-namespace/my-api-protection/x-wristband + customClaims: + "aud": + value: internal + "born": + selector: auth.identity.metadata.creationTimestamp + tokenDuration: 300 + signingKeyRefs: + - name: my-signing-key + algorithm: ES256 + - name: my-old-signing-key + algorithm: RS256 ``` The signing key names listed in `signingKeyRefs` must match the names of Kubernetes `Secret` resources created in the same namespace, where each secret contains a `key.pem` entry that holds the value of the private key that will be used to sign the wristbands issued, formatted as [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail). The first key in this list will be used to sign the wristbands, while the others are kept to support key rotation. @@ -702,44 +733,6 @@ For each protected API configured for the Festival Wristband issuing, Authorino - **JSON Web Key Set (JWKS) well-known endpoint:**
https://authorino-oidc.default.svc:8083/{namespace}/{api-protection-name}/{response-config-name}/.well-known/openid-connect/certs -### _Extra:_ Response wrappers ([`wrapper`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Response_Wrapper) and [`wrapperKey`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Response_Wrapper)) - -#### Added HTTP headers - -By default, Authorino dynamic responses (injected JSON and Festival Wristband tokens) are passed back to Envoy, stringified, as injected HTTP headers. This can be made explicit by setting the `wrapper` property of the response config to `httpHeader`. - -The property `wrapperKey` controls the name of the HTTP header, with default to the name of dynamic response config when omitted. - -#### Envoy Dynamic Metadata - -Authorino dynamic responses (injected JSON and Festival Wristband tokens) can be passed back to Envoy in the form of Envoy Dynamic Metadata. To do so, set the `wrapper` property of the response config to `envoyDynamicMetadata`. - -A response config with `wrapper=envoyDynamicMetadata` and `wrapperKey=auth-data` in the `AuthConfig` can be configured in the Envoy route or virtual host setting to be passed to rate limiting filter as below. The metadata content is expected to be a dynamic JSON injected by Authorino containing `{ "auth-data": { "api-key-ns": string, "api-key-name": string } }`. (See the response config `a-json-returned-as-envoy-metadata` in the example for the [JSON injection feature](#json-injection-responsejson) above) - -```yaml -# Envoy config snippet to inject `user_namespace` and `username` rate limit descriptors from metadata returned by Authorino -rate_limits: -- actions: - - metadata: - metadata_key: - key: "envoy.filters.http.ext_authz" - path: - - key: auth-data - - key: api-key-ns - descriptor_key: user_namespace - - metadata: - metadata_key: - key: "envoy.filters.http.ext_authz" - path: - - key: auth-data - - key: api-key-name - descriptor_key: username -``` - -### _Extra:_ Custom denial status ([`denyWith`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#DenyWith)) - -By default, Authorino will inform Envoy to respond with `401 Unauthorized` or `403 Forbidden` respectively when the identity verification (phase i of the [Auth Pipeline](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time)) or authorization (phase ii) fail. These can be customized by specifying `spec.denyWith` in the `AuthConfig`. - ## Callbacks (`callbacks`) ### HTTP endpoints (`callbacks.http`) @@ -752,25 +745,24 @@ Example: ```yaml spec: - identity: […] + authentication: […] authorization: […] callbacks: - - name: log + "log": http: - endpoint: http://logsys + url: http://logsys method: POST body: - valueFrom: - authJSON: | + selector: | \{"requestId":context.request.http.id,"username":"{auth.identity.username}","authorizationResult":{auth.authorization}\} - - name: important-forbidden + "important-forbidden": when: - - selector: auth.authorization.important-policy - operator: eq - value: "false" + - selector: auth.authorization.important-policy + operator: eq + value: "false" http: - endpoint: "http://monitoring/important?forbidden-user={auth.identity.username}" + url: "http://monitoring/important?forbidden-user={auth.identity.username}" ``` ## Common feature: Priorities @@ -791,83 +783,79 @@ Priorities can be set using the `priority` property available in all evaluator c Consider the following example to understand how priorities work: ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 +apiVersion: authorino.kuadrant.io/v1beta2 kind: AuthConfig metadata: name: talker-api-protection spec: hosts: - - talker-api - identity: - - name: tier-1 + - talker-api + authentication: + "tier-1": priority: 0 apiKey: selector: matchLabels: tier: "1" - - name: tier-2 + "tier-2": priority: 1 apiKey: selector: matchLabels: tier: "2" - - name: tier-3 + "tier-3": priority: 1 apiKey: selector: matchLabels: tier: "3" metadata: - - name: first + "first": http: - endpoint: http://talker-api:3000 - method: GET - - name: second + url: http://talker-api:3000 + "second": priority: 1 http: - endpoint: http://talker-api:3000/first_uuid={auth.metadata.first.uuid} - method: GET + url: http://talker-api:3000/first_uuid={auth.metadata.first.uuid} authorization: - - name: allowed-endpoints + "allowed-endpoints": when: - - selector: context.request.http.path - operator: neq - value: /hi - - selector: context.request.http.path - operator: neq - value: /hello - - selector: context.request.http.path - operator: neq - value: /aloha - - selector: context.request.http.path - operator: neq - value: /ciao - json: - rules: - - selector: deny - operator: eq - value: "true" - - name: more-expensive-policy # no point in evaluating this one if it's not an allowed endpoint + - selector: context.request.http.path + operator: neq + value: /hi + - selector: context.request.http.path + operator: neq + value: /hello + - selector: context.request.http.path + operator: neq + value: /aloha + - selector: context.request.http.path + operator: neq + value: /ciao + patternMatching: + patterns: + - selector: deny + operator: eq + value: "true" + "more-expensive-policy": # no point in evaluating this one if it's not an allowed endpoint priority: 1 opa: inlineRego: | allow { true } response: - - name: x-auth-data - json: - properties: - - name: tier - valueFrom: - authJSON: auth.identity.metadata.labels.tier - - name: first-uuid - valueFrom: - authJSON: auth.metadata.first.uuid - - name: second-uuid - valueFrom: - authJSON: auth.metadata.second.uuid - - name: second-path - valueFrom: - authJSON: auth.metadata.second.path + success: + headers: + "x-auth-data": + json: + properties: + "tier": + selector: auth.identity.metadata.labels.tier + "first-uuid": + selector: auth.metadata.first.uuid + "second-uuid": + selector: auth.metadata.second.uuid + "second-path": + selector: auth.metadata.second.path ``` For the `AuthConfig` above, @@ -885,7 +873,7 @@ _Conditions_, named `when` in the AuthConfig API, are sets of expressions (JSON The scope for a set of `when` conditions can be the entire `AuthConfig` ("top-level conditions") or a particular evaluator of any phase of the auth pipeline. Each expression is a tuple composed of: -- a `selector`, to fetch from the Authorization JSON – see [Common feature: JSON paths](#common-feature-json-paths-valuefromauthjson) for details about syntax; +- a `selector`, to fetch from the Authorization JSON – see [Common feature: JSON paths](#common-feature-json-paths-selector) for details about syntax; - an `operator` – `eq` (_equals_), `neq` (_not equal_); `incl` (_includes_) and `excl` (_excludes_), for arrays; and `matches`, for regular expressions; - a fixed comparable `value` @@ -908,32 +896,32 @@ ii) to skip parts of an `AuthConfig` (i.e. a specific evaluator): ```yaml spec: metadata: - - name: metadata-source - http: - endpoint: https://my-metadata-source.io - when: # only fetch the external metadata if the context is HTTP method other than OPTIONS - - selector: context.request.http.method - operator: neq - value: OPTIONS + "metadata-source": + http: + url: https://my-metadata-source.io + when: # only fetch the external metadata if the context is HTTP method other than OPTIONS + - selector: context.request.http.method + operator: neq + value: OPTIONS ``` iii) to enforce a particular evaluator only in certain contexts (really the same as the above, though to a different use case): ```yaml spec: - identity: - - name: authn-meth-1 - apiKey: {...} # this authn method only valid for POST requests to /foo[/*] - when: - - selector: context.request.http.path - operator: matches - value: ^/foo(/.*)?$ - - selector: context.request.http.method - operator: eq - value: POST + authentication: + "authn-meth-1": + apiKey: {…} # this authn method only valid for POST requests to /foo[/*] + when: + - selector: context.request.http.path + operator: matches + value: ^/foo(/.*)?$ + - selector: context.request.http.method + operator: eq + value: POST - - name: authn-meth-2 - oidc: {...} + "authn-meth-2": + jwt: {…} ``` iv) to avoid repetition while defining patterns for conditions: @@ -947,19 +935,19 @@ spec: value: ^/pets/\d+(/.*)$ metadata: - - name: pets-info - when: - - patternRef: a-pet - http: - endpoint: https://pets-info.io?petId={context.request.http.path.@extract:{"sep":"/","pos":2}} + "pets-info": + when: + - patternRef: a-pet + http: + url: https://pets-info.io?petId={context.request.http.path.@extract:{"sep":"/","pos":2}} authorization: - - name: pets-owners-only - when: - - patternRef: a-pet - opa: - inlineRego: | - allow { input.metadata["pets-info"].ownerid == input.auth.identity.userid } + "pets-owners-only": + when: + - patternRef: a-pet + opa: + inlineRego: | + allow { input.metadata["pets-info"].ownerid == input.auth.identity.userid } ``` v) mixing and combining literal expressions and refs: @@ -979,30 +967,30 @@ spec: value: "" authorization: - - name: my-policy-1 - when: # authenticated access to /foo controlled by policy - - patternRef: foo - json: {...} + "my-policy-1": + when: # authenticated access to /foo controlled by policy + - patternRef: foo + patternMatching: {…} ``` vi) to avoid evaluating unnecessary identity checks when the user can indicate the preferred authentication method (again the pattern of skipping based upon the context): ```yaml spec: - identity: - - name: jwt - when: - - selector: context.request.http.headers.authorization - operator: matches - value: JWT .+ - oidc: {...} + authentication: + "jwt": + when: + - selector: context.request.http.headers.authorization + operator: matches + value: JWT .+ + jwt: {…} - - name: api-key - when: - - selector: context.request.http.headers.authorization - operator: matches - value: APIKEY .+ - apiKey: {...} + "api-key": + when: + - selector: context.request.http.headers.authorization + operator: matches + value: APIKEY .+ + apiKey: {…} ``` ## Common feature: Caching (`cache`) @@ -1018,27 +1006,26 @@ spec: hosts: - my-api.io - identity: [...] + authentication: […] metadata: - - name: external-metadata - http: - endpoint: http://my-external-source?search={context.request.http.path} - cache: - key: - valueFrom: { authJSON: context.request.http.path } - ttl: 300 + "external-metadata": + http: + url: http://my-external-source?search={context.request.http.path} + cache: + key: + selector: context.request.http.path + ttl: 300 authorization: - - name: complex-policy - opa: - externalRegistry: - endpoint: http://my-policy-registry - cache: - key: - valueFrom: - authJSON: "{auth.identity.group}-{context.request.http.method}-{context.request.http.path}" - ttl: 60 + "complex-policy": + opa: + externalRegistry: + url: http://my-policy-registry + cache: + key: + selector: "{auth.identity.group}-{context.request.http.method}-{context.request.http.path}" + ttl: 60 ``` The example above sets caching for the 'external-metadata' metadata config and for the 'complex-policy' authorization policy. In the case of 'external-metadata', the cache key is the path of the original HTTP request being authorized by Authorino (fetched dynamically from the [Authorization JSON](./architecture.md#the-authorization-json)); i.e., after obtaining a metadata object from the external source for a given contextual HTTP path one first time, whenever that same HTTP path repeats in a subsequent request, Authorino will use the cached object instead of sending a request again to the external source of metadata. After 5 minutes (300 seconds), the cache entry will expire and Authorino will fetch again from the source if requested. @@ -1058,42 +1045,43 @@ By default, Authorino will only export metrics down to the level of the AuthConf E.g.: ```yaml -apiVersion: authorino.kuadrant.io/v1beta1 +apiVersion: authorino.kuadrant.io/v1beta2 kind: AuthConfig metadata: name: my-authconfig namespace: my-ns spec: metadata: - - name: my-external-metadata - http: - endpoint: http://my-external-source?search={context.request.http.path} - metrics: true + "my-external-metadata": + http: + url: http://my-external-source?search={context.request.http.path} + metrics: true ``` The above will enable the metrics `auth_server_evaluator_duration_seconds` (histogram) and `auth_server_evaluator_total` (counter) with labels `namespace="my-ns"`, `authconfig="my-authconfig"`, `evaluator_type="METADATA_GENERIC_HTTP"` and `evaluator_name="my-external-metadata"`. The same pattern works for other types of evaluators. Find below the list of all types and corresponding label constant used in the metric: -| Evaluator type | Metric's `evaluator_type` label | -|----------------------------|---------------------------------| -| `identity.apiKey` | IDENTITY_APIKEY | -| `identity.kubernetes` | IDENTITY_KUBERNETES | -| `identity.oidc` | IDENTITY_OIDC | -| `identity.oauth2` | IDENTITY_OAUTH2 | -| `identity.mtls` | IDENTITY_MTLS | -| `identity.hmac` | IDENTITY_HMAC | -| `identity.plain` | IDENTITY_PLAIN | -| `identity.anonymous` | IDENTITY_NOOP | -| `metadata.http` | METADATA_GENERIC_HTTP | -| `metadata.userInfo` | METADATA_USERINFO | -| `metadata.uma` | METADATA_UMA | -| `authorization.json` | AUTHORIZATION_JSON | -| `authorization.opa` | AUTHORIZATION_OPA | -| `authorization.kubernetes` | AUTHORIZATION_KUBERNETES | -| `response.json` | RESPONSE_JSON | -| `response.wristband` | RESPONSE_WRISTBAND | - -Metrics at the level of the evaluators can also be enforced to an entire Authorino instance, by setting the --deep-metrics-enabled command-line flag. In this case, regardless of the value of the field `spec.(identity|metadata|authorization|response).metrics` in the AuthConfigs, individual metrics for all evaluators of all AuthConfigs will be exported. +| Evaluator type | Metric's `evaluator_type` label | +|-----------------------------------------------|---------------------------------| +| `authentication.apiKey` | IDENTITY_APIKEY | +| `authentication.kubernetesTokenReview` | IDENTITY_KUBERNETES | +| `authentication.jwt` | IDENTITY_OIDC | +| `authentication.oauth2Introspection` | IDENTITY_OAUTH2 | +| `authentication.x509` | IDENTITY_MTLS | +| `authentication.plain` | IDENTITY_PLAIN | +| `authentication.anonymous` | IDENTITY_NOOP | +| `metadata.http` | METADATA_GENERIC_HTTP | +| `metadata.userInfo` | METADATA_USERINFO | +| `metadata.uma` | METADATA_UMA | +| `authorization.patternMatching` | AUTHORIZATION_JSON | +| `authorization.opa` | AUTHORIZATION_OPA | +| `authorization.kubernetesSubjectAccessReview` | AUTHORIZATION_KUBERNETES | +| `authorization.spicedb` | AUTHORIZATION_AUTHZED | +| `response.success..plain` | RESPONSE_PLAIN | +| `response.success..json` | RESPONSE_JSON | +| `response.success..wristband` | RESPONSE_WRISTBAND | + +Metrics at the level of the evaluators can also be enforced to an entire Authorino instance, by setting the --deep-metrics-enabled command-line flag. In this case, regardless of the value of the field `spec.(authentication|metadata|authorization|response).metrics` in the AuthConfigs, individual metrics for all evaluators of all AuthConfigs will be exported. For more information about metrics exported by Authorino, see [Observability](./user-guides/observability.md#metrics). From 99e5ecf7ac46eef80be6ee83d4933520cedb4d14 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 10:25:51 +0200 Subject: [PATCH 11/35] remove: unimplemented spec.authentication.jwt.jwksUrl --- api/v1beta2/auth_config_types.go | 5 ----- install/crd/authorino.kuadrant.io_authconfigs.yaml | 5 ----- install/manifests.yaml | 5 ----- 3 files changed, 15 deletions(-) diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index 30be9637..1e1df569 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -337,11 +337,6 @@ type ApiKeyAuthenticationSpec struct { // Settings to fetch the JSON Web Key Set (JWKS) for the JWT authentication. type JwtAuthenticationSpec struct { - // URL of the JSON Web Ket Set (JWKS) endpoint. - // Use this if the issuer of the JWT tokens is not an OIDC provider or does not implement OIDC Discovery. - // +optional - JwksUrl string `json:"jwksUrl,omitempty"` - // 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 diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 1675b226..3cfaaf06 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2504,11 +2504,6 @@ spec: with the value of the "iss" (issuer) claim of the discovered OpenID Connect configuration. type: string - jwksUrl: - description: URL of the JSON Web Ket Set (JWKS) endpoint. - Use this if the issuer of the JWT tokens is not an OIDC - provider or does not implement OIDC Discovery. - type: string ttl: description: Decides how long to wait before refreshing the JWKS (in seconds). If omitted, Authorino will never diff --git a/install/manifests.yaml b/install/manifests.yaml index 44d7d525..2e439849 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2735,11 +2735,6 @@ spec: with the value of the "iss" (issuer) claim of the discovered OpenID Connect configuration. type: string - jwksUrl: - description: URL of the JSON Web Ket Set (JWKS) endpoint. - Use this if the issuer of the JWT tokens is not an OIDC - provider or does not implement OIDC Discovery. - type: string ttl: description: Decides how long to wait before refreshing the JWKS (in seconds). If omitted, Authorino will never From 8eaeca12badc03cb947ef88afefb1a8f2991f78e Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 10:26:35 +0200 Subject: [PATCH 12/35] remove: unimplemented spec.authentication.credentials.customHeader.prefix --- api/v1beta2/auth_config_types.go | 3 +-- api/v1beta2/zz_generated.deepcopy.go | 1 - install/crd/authorino.kuadrant.io_authconfigs.yaml | 8 -------- install/manifests.yaml | 8 -------- 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index 1e1df569..097d9613 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -298,8 +298,7 @@ type Prefixed struct { } type CustomHeader struct { - Named `json:""` - Prefixed `json:""` + Named `json:""` } type ExtendedProperties NamedValuesOrSelectors diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 8fd2feb8..90aa19d4 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -543,7 +543,6 @@ func (in *Credentials) DeepCopy() *Credentials { func (in *CustomHeader) DeepCopyInto(out *CustomHeader) { *out = *in out.Named = in.Named - out.Prefixed = in.Prefixed } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomHeader. diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 3cfaaf06..883abe65 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2454,8 +2454,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object @@ -2981,8 +2979,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object @@ -3452,8 +3448,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object @@ -3759,8 +3753,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object diff --git a/install/manifests.yaml b/install/manifests.yaml index 2e439849..9368bcbc 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2685,8 +2685,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object @@ -3241,8 +3239,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object @@ -3736,8 +3732,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object @@ -4056,8 +4050,6 @@ spec: properties: name: type: string - prefix: - type: string required: - name type: object From d99b225b5135a575fc2896cb848394abb993e7df Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 10:28:54 +0200 Subject: [PATCH 13/35] remove: unimplemented spec.(metadata|authorization.opa.externalPolicy|callbacks).http.insecure --- api/v1beta2/auth_config_types.go | 3 --- install/crd/authorino.kuadrant.io_authconfigs.yaml | 12 ------------ install/manifests.yaml | 12 ------------ 3 files changed, 27 deletions(-) diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index 097d9613..d13219c5 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -470,9 +470,6 @@ type HttpEndpointSpec struct { // If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. // +optional Credentials Credentials `json:"credentials,omitempty"` - - // Insecure HTTP connection (i.e. disables TLS verification) - Insecure bool `json:"insecure,omitempty"` } // +kubebuilder:validation:Enum:=GET;POST;PUT;PATCH;DELETE;HEAD;OPTIONS;CONNECT;TRACE diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 883abe65..82af8747 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -3010,10 +3010,6 @@ spec: type: object description: Custom headers in the HTTP request. type: object - insecure: - description: Insecure HTTP connection (i.e. disables - TLS verification) - type: boolean method: default: GET description: 'HTTP verb used in the request to the service. @@ -3478,10 +3474,6 @@ spec: type: object description: Custom headers in the HTTP request. type: object - insecure: - description: Insecure HTTP connection (i.e. disables TLS - verification) - type: boolean method: default: GET description: 'HTTP verb used in the request to the service. @@ -3783,10 +3775,6 @@ spec: type: object description: Custom headers in the HTTP request. type: object - insecure: - description: Insecure HTTP connection (i.e. disables TLS - verification) - type: boolean method: default: GET description: 'HTTP verb used in the request to the service. diff --git a/install/manifests.yaml b/install/manifests.yaml index 9368bcbc..bafb6042 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -3270,10 +3270,6 @@ spec: type: object description: Custom headers in the HTTP request. type: object - insecure: - description: Insecure HTTP connection (i.e. disables - TLS verification) - type: boolean method: default: GET description: 'HTTP verb used in the request to the service. @@ -3762,10 +3758,6 @@ spec: type: object description: Custom headers in the HTTP request. type: object - insecure: - description: Insecure HTTP connection (i.e. disables TLS - verification) - type: boolean method: default: GET description: 'HTTP verb used in the request to the service. @@ -4080,10 +4072,6 @@ spec: type: object description: Custom headers in the HTTP request. type: object - insecure: - description: Insecure HTTP connection (i.e. disables TLS - verification) - type: boolean method: default: GET description: 'HTTP verb used in the request to the service. From 6202b2667c2e12ac6924b2e2a3dd43f1197727cd Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 10:42:47 +0200 Subject: [PATCH 14/35] Add description to not use unimplemented fields of the OPA external policy HTTP settings --- api/v1beta2/auth_config_types.go | 5 ++++- install/crd/authorino.kuadrant.io_authconfigs.yaml | 7 ++++++- install/manifests.yaml | 7 ++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index d13219c5..a13c065f 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -561,7 +561,10 @@ type OpaAuthorizationSpec struct { // The Rego document must NOT include the "package" declaration in line 1. Rego string `json:"rego,omitempty"` - // External registry of OPA policies. + // Settings for fetching the OPA policy from an external registry. + // Use it alternatively to 'inlineRego'. + // 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'. External *ExternalOpaPolicy `json:"externalPolicy,omitempty"` // Returns the value of all Rego rules in the virtual document. Values can be read in subsequent evaluators/phases of the Auth Pipeline. diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 82af8747..c52c9057 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2899,7 +2899,12 @@ spec: precompile) and at runtime. type: boolean externalPolicy: - description: External registry of OPA policies. + description: 'Settings for fetching the OPA policy from + an external registry. Use it alternatively to ''inlineRego''. + 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 diff --git a/install/manifests.yaml b/install/manifests.yaml index bafb6042..cc45455c 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -3159,7 +3159,12 @@ spec: precompile) and at runtime. type: boolean externalPolicy: - description: External registry of OPA policies. + description: 'Settings for fetching the OPA policy from + an external registry. Use it alternatively to ''inlineRego''. + 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 From c22d5003f55ec103971bb1db4df93e639a99a634 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 10:43:11 +0200 Subject: [PATCH 15/35] docs: fixup: opa.externalPolicy --- docs/features.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features.md b/docs/features.md index c19f3c0d..f4d9aeed 100644 --- a/docs/features.md +++ b/docs/features.md @@ -448,9 +448,9 @@ spec: You can model authorization policies in [Rego language](https://www.openpolicyagent.org/docs/latest/policy-language/) and add them as part of the protection of your APIs. -Policies can be either declared in-line in Rego language (`inlineRego`) or as an HTTP endpoint where Authorino will fetch the source code of the policy in reconciliation-time (`externalRegistry`). +Policies can be either declared in-line in Rego language (`inlineRego`) or as an HTTP endpoint where Authorino will fetch the source code of the policy in reconciliation-time (`externalPolicy`). -Policies pulled from external registries can be configured to be automatically refreshed (pulled again from the external registry), by setting the `authorization.opa.externalRegistry.ttl` field (given in seconds, default: `0` – i.e. auto-refresh disabled). +Policies pulled from external registries can be configured to be automatically refreshed (pulled again from the external registry), by setting the `authorization.opa.externalPolicy.ttl` field (given in seconds, default: `0` – i.e. auto-refresh disabled). Authorino's built-in OPA module precompiles the policies during reconciliation of the AuthConfig and caches the precompiled policies for fast evaluation in runtime, where they receive the Authorization JSON as input. @@ -1020,7 +1020,7 @@ spec: authorization: "complex-policy": opa: - externalRegistry: + externalPolicy: url: http://my-policy-registry cache: key: From b556c260af06bc8930f44646a364254b0abc2030 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 10:55:22 +0200 Subject: [PATCH 16/35] docs: fixup: wristband.issuer example --- docs/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features.md b/docs/features.md index f4d9aeed..3f785eb7 100644 --- a/docs/features.md +++ b/docs/features.md @@ -711,7 +711,7 @@ spec: headers: "x-wristband": wristband: - issuer: https://authorino-oidc.default.svc:8083/my-namespace/my-api-protection/x-wristband + issuer: https://authorino-oidc.default.svc:8083/my-namespace/my-api-protection/my-wristband customClaims: "aud": value: internal From 3527edf0a023b323fa4c29fa48397738efe4c130 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 10:56:00 +0200 Subject: [PATCH 17/35] Add deprecation notice of v1beta1 reponse.wrapperKey field --- api/v1beta1/auth_config_types.go | 1 + install/crd/authorino.kuadrant.io_authconfigs.yaml | 2 ++ install/manifests.yaml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/api/v1beta1/auth_config_types.go b/api/v1beta1/auth_config_types.go index f0535d99..ff89df06 100644 --- a/api/v1beta1/auth_config_types.go +++ b/api/v1beta1/auth_config_types.go @@ -599,6 +599,7 @@ type Response struct { Wrapper Response_Wrapper `json:"wrapper,omitempty"` // The name of key used in the wrapped response (name of the HTTP header or property of the Envoy Dynamic Metadata JSON). // If omitted, it will be set to the name of the configuration. + // [DEPRECATED] Starting in v1beta2, use the name of the response config instead. WrapperKey string `json:"wrapperKey,omitempty"` Wristband *Response_Wristband `json:"wristband,omitempty"` diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index c52c9057..796462e7 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2067,6 +2067,8 @@ spec: description: The name of key used in the wrapped response (name of the HTTP header or property of the Envoy Dynamic Metadata JSON). If omitted, it will be set to the name of the configuration. + [DEPRECATED] Starting in v1beta2, use the name of the response + config instead. type: string wristband: properties: diff --git a/install/manifests.yaml b/install/manifests.yaml index cc45455c..8704f330 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2250,6 +2250,8 @@ spec: description: The name of key used in the wrapped response (name of the HTTP header or property of the Envoy Dynamic Metadata JSON). If omitted, it will be set to the name of the configuration. + [DEPRECATED] Starting in v1beta2, use the name of the response + config instead. type: string wristband: properties: From c70f9b6712986f5abea8c69f68de8d6d3450842f Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 11:18:31 +0200 Subject: [PATCH 18/35] Revert "docs: fixup: wristband.issuer example" This reverts commit b556c260af06bc8930f44646a364254b0abc2030. --- docs/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features.md b/docs/features.md index 3f785eb7..f4d9aeed 100644 --- a/docs/features.md +++ b/docs/features.md @@ -711,7 +711,7 @@ spec: headers: "x-wristband": wristband: - issuer: https://authorino-oidc.default.svc:8083/my-namespace/my-api-protection/my-wristband + issuer: https://authorino-oidc.default.svc:8083/my-namespace/my-api-protection/x-wristband customClaims: "aud": value: internal From cf14ba3c46196290f4d8c29c6239c2e4dc93a54c Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 13:26:30 +0200 Subject: [PATCH 19/35] remove: unimplemented spec.response.success.headers.prefix --- api/v1beta2/auth_config_types.go | 3 --- install/crd/authorino.kuadrant.io_authconfigs.yaml | 4 ---- install/manifests.yaml | 4 ---- 3 files changed, 11 deletions(-) diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index a13c065f..25a0804a 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -694,9 +694,6 @@ type WrappedSuccessResponseSpec struct { } type HeaderSuccessResponseSpec struct { - // Prefix of the value to be added to the HTTP header - Prefix string `json:"prefix,omitempty"` - SuccessResponseSpec `json:",omitempty"` } diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 796462e7..4ac35898 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -4306,10 +4306,6 @@ spec: description: Static value x-kubernetes-preserve-unknown-fields: true type: object - prefix: - description: Prefix of the value to be added to the - HTTP header - type: string priority: default: 0 description: Priority group of the config. All configs diff --git a/install/manifests.yaml b/install/manifests.yaml index 8704f330..ccbeb285 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -4653,10 +4653,6 @@ spec: description: Static value x-kubernetes-preserve-unknown-fields: true type: object - prefix: - description: Prefix of the value to be added to the - HTTP header - type: string priority: default: 0 description: Priority group of the config. All configs From b61838f3078da6f64bad8822a812f028b186de72 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 13:51:41 +0200 Subject: [PATCH 20/35] Add field spec.response.success.(headers|dynamicMetadata).key --- api/v1beta1/auth_config_types.go | 1 - api/v1beta2/auth_config_conversion.go | 3 +- api/v1beta2/auth_config_conversion_test.go | 22 ++++++----- api/v1beta2/auth_config_types.go | 4 ++ docs/features.md | 38 +++++++++++-------- .../authorino.kuadrant.io_authconfigs.yaml | 14 ++++++- install/manifests.yaml | 14 ++++++- 7 files changed, 65 insertions(+), 31 deletions(-) diff --git a/api/v1beta1/auth_config_types.go b/api/v1beta1/auth_config_types.go index ff89df06..f0535d99 100644 --- a/api/v1beta1/auth_config_types.go +++ b/api/v1beta1/auth_config_types.go @@ -599,7 +599,6 @@ type Response struct { Wrapper Response_Wrapper `json:"wrapper,omitempty"` // The name of key used in the wrapped response (name of the HTTP header or property of the Envoy Dynamic Metadata JSON). // If omitted, it will be set to the name of the configuration. - // [DEPRECATED] Starting in v1beta2, use the name of the response config instead. WrapperKey string `json:"wrapperKey,omitempty"` Wristband *Response_Wristband `json:"wristband,omitempty"` diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go index 32e19ac6..308c6bd2 100644 --- a/api/v1beta2/auth_config_conversion.go +++ b/api/v1beta2/auth_config_conversion.go @@ -835,7 +835,7 @@ func convertSuccessResponseTo(name string, src SuccessResponseSpec, wrapper stri Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefTo), Cache: convertEvaluatorCachingTo(src.Cache), Wrapper: v1beta1.Response_Wrapper(wrapper), - WrapperKey: name, + WrapperKey: src.Key, } switch src.GetMethod() { @@ -878,6 +878,7 @@ func convertSuccessResponseFrom(src *v1beta1.Response) (string, SuccessResponseS Conditions: utils.Map(src.Conditions, convertPatternExpressionOrRefFrom), Cache: convertEvaluatorCachingFrom(src.Cache), }, + Key: src.WrapperKey, } switch src.GetType() { diff --git a/api/v1beta2/auth_config_conversion_test.go b/api/v1beta2/auth_config_conversion_test.go index 359f5b77..45d68c3f 100644 --- a/api/v1beta2/auth_config_conversion_test.go +++ b/api/v1beta2/auth_config_conversion_test.go @@ -367,13 +367,15 @@ func authConfig() *AuthConfig { "success": { "dynamicMetadata": { "username": { + "key": "", "plain": { "selector": "auth.identity.username" } } }, "headers": { - "festivalWristband": { + "festival-wristband": { + "key": "x-wristband-token", "wristband": { "customClaims": { "scope": { @@ -386,7 +388,7 @@ func authConfig() *AuthConfig { "selector": "auth.identity.username" } }, - "issuer": "https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/wristband", + "issuer": "https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/festival-wristband", "signingKeyRefs": [ { "algorithm": "ES256", @@ -409,9 +411,11 @@ func authConfig() *AuthConfig { "selector": "auth.identity.username" } } - } + }, + "key": "" }, "x-auth-service": { + "key": "", "plain": { "value": "Authorino" } @@ -883,10 +887,10 @@ func hubAuthConfig() *v1beta1.AuthConfig { "response": [ { "metrics": false, - "name": "festivalWristband", + "name": "festival-wristband", "priority": 0, "wrapper": "httpHeader", - "wrapperKey": "festivalWristband", + "wrapperKey": "x-wristband-token", "wristband": { "customClaims": [ { @@ -908,7 +912,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { } } ], - "issuer": "https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/wristband", + "issuer": "https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/festival-wristband", "signingKeyRefs": [ { "algorithm": "ES256", @@ -928,7 +932,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { }, "priority": 0, "wrapper": "envoyDynamicMetadata", - "wrapperKey": "username" + "wrapperKey": "" }, { "json": { @@ -957,7 +961,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { "name": "x-auth-data", "priority": 0, "wrapper": "httpHeader", - "wrapperKey": "x-auth-data" + "wrapperKey": "" }, { "metrics": false, @@ -968,7 +972,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { }, "priority": 0, "wrapper": "httpHeader", - "wrapperKey": "x-auth-service" + "wrapperKey": "" } ], "when": [ diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index 25a0804a..d97d2fd3 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -701,6 +701,10 @@ type HeaderSuccessResponseSpec struct { type SuccessResponseSpec struct { CommonEvaluatorSpec `json:""` AuthResponseMethodSpec `json:""` + + // 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. + Key string `json:"key,omitempty"` } func (s *SuccessResponseSpec) GetMethod() AuthResponseMethod { diff --git a/docs/features.md b/docs/features.md index f4d9aeed..da5cb0ca 100644 --- a/docs/features.md +++ b/docs/features.md @@ -574,30 +574,36 @@ Successful authorization custom responses can be set based on any of the support Set custom responses as HTTP headers injected in the request post-successful authorization by specifying one of the supported methods under `response.success.headers`. +The name of the response config (default) or the value of the `key` option (if provided) will used as the name of the header. + #### Envoy Dynamic Metadata Authorino custom response methods can also be used to propagate [Envoy Dynamic Metadata](https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata). To do so, set one of the supported methods under `response.success.dynamicMetadata`. -A custom response exported as Envoy Dynamic Metadata can be configured in the Envoy route or virtual host configuration, to be passed. E.g., for reading metadata emitted by the authorization service in the following scheme: `{ "auth-data": { "api-key-ns": string, "api-key-name": string } }` for a rate limiting: +The name of the response config (default) or the value of the `key` option (if provided) will used as the name of the root property of the dynamic metadata content. + +A custom response exported as Envoy Dynamic Metadata can be set in the Envoy route or virtual host configuration as input to a consecutive filter in the filter chain. + +E.g., to read metadata emitted by the authorization service with scheme `{ "auth-data": { "api-key-ns": string, "api-key-name": string } }`, as input in a rate limit configuration placed in the filter chain after the external authorization, the Envoy config may look like the following: ```yaml -# Envoy config snippet to inject `user_namespace` and `username` rate limit descriptors from metadata returned by Authorino +# Envoy config snippet to inject `user_namespace` and `username` rate limit descriptors from metadata emitted by Authorino rate_limits: - actions: - - metadata: - metadata_key: - key: "envoy.filters.http.ext_authz" - path: - - key: auth-data - - key: api-key-ns - descriptor_key: user_namespace - - metadata: - metadata_key: - key: "envoy.filters.http.ext_authz" - path: - - key: auth-data - - key: api-key-name - descriptor_key: username + - metadata: + metadata_key: + key: "envoy.filters.http.ext_authz" + path: + - key: auth-data # root of the dynamic metadata object, as declared in a custom response config of the AuthConfig (name or key) + - key: api-key-ns + descriptor_key: user_namespace + - metadata: + metadata_key: + key: "envoy.filters.http.ext_authz" + path: + - key: auth-data # root of the dynamic metadata object, as declared in a custom response config of the AuthConfig (name or key) + - key: api-key-name + descriptor_key: username ``` #### Custom denial status ([`response.unauthenticated`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#DenyWithSpec) and [`response.unauthorized`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta2?utm_source=gopls#DenyWithSpec)) diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 4ac35898..1b557e1c 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2067,8 +2067,6 @@ spec: description: The name of key used in the wrapped response (name of the HTTP header or property of the Envoy Dynamic Metadata JSON). If omitted, it will be set to the name of the configuration. - [DEPRECATED] Starting in v1beta2, use the name of the response - config instead. type: string wristband: properties: @@ -4074,6 +4072,12 @@ spec: 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 @@ -4284,6 +4288,12 @@ spec: 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 diff --git a/install/manifests.yaml b/install/manifests.yaml index ccbeb285..7527a0e9 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2250,8 +2250,6 @@ spec: description: The name of key used in the wrapped response (name of the HTTP header or property of the Envoy Dynamic Metadata JSON). If omitted, it will be set to the name of the configuration. - [DEPRECATED] Starting in v1beta2, use the name of the response - config instead. type: string wristband: properties: @@ -4396,6 +4394,12 @@ spec: 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 @@ -4631,6 +4635,12 @@ spec: 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 From 7e92eaf8a4e245f4dd2faabab6db06a6c6e791dd Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 14:01:59 +0200 Subject: [PATCH 21/35] docs: update for v1beta2: 'Getting started' --- docs/getting-started.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 31e44189..552d9331 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -346,25 +346,25 @@ For authentication based on OpenID Connect (OIDC) JSON Web Tokens (JWT), plus on ```sh kubectl -n myapp apply -f -< Date: Tue, 5 Sep 2023 14:41:01 +0200 Subject: [PATCH 22/35] =?UTF-8?q?docs:=20fixup:=20inlineRego=20=E2=86=92?= =?UTF-8?q?=20=20rego?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1beta2/auth_config_types.go | 2 +- docs/features.md | 6 +++--- install/crd/authorino.kuadrant.io_authconfigs.yaml | 2 +- install/manifests.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index d97d2fd3..dbec9df3 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -562,7 +562,7 @@ type OpaAuthorizationSpec struct { Rego string `json:"rego,omitempty"` // Settings for fetching the OPA policy from an external registry. - // Use it alternatively to 'inlineRego'. + // 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'. External *ExternalOpaPolicy `json:"externalPolicy,omitempty"` diff --git a/docs/features.md b/docs/features.md index da5cb0ca..1505ab46 100644 --- a/docs/features.md +++ b/docs/features.md @@ -448,7 +448,7 @@ spec: You can model authorization policies in [Rego language](https://www.openpolicyagent.org/docs/latest/policy-language/) and add them as part of the protection of your APIs. -Policies can be either declared in-line in Rego language (`inlineRego`) or as an HTTP endpoint where Authorino will fetch the source code of the policy in reconciliation-time (`externalPolicy`). +Policies can be either declared in-line in Rego language (`rego`) or as an HTTP endpoint where Authorino will fetch the source code of the policy in reconciliation-time (`externalPolicy`). Policies pulled from external registries can be configured to be automatically refreshed (pulled again from the external registry), by setting the `authorization.opa.externalPolicy.ttl` field (given in seconds, default: `0` – i.e. auto-refresh disabled). @@ -846,7 +846,7 @@ spec: "more-expensive-policy": # no point in evaluating this one if it's not an allowed endpoint priority: 1 opa: - inlineRego: | + rego: | allow { true } response: success: @@ -952,7 +952,7 @@ spec: when: - patternRef: a-pet opa: - inlineRego: | + rego: | allow { input.metadata["pets-info"].ownerid == input.auth.identity.userid } ``` diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 1b557e1c..90a89374 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2900,7 +2900,7 @@ spec: type: boolean externalPolicy: description: 'Settings for fetching the OPA policy from - an external registry. Use it alternatively to ''inlineRego''. + 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 diff --git a/install/manifests.yaml b/install/manifests.yaml index 7527a0e9..ae05d895 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -3160,7 +3160,7 @@ spec: type: boolean externalPolicy: description: 'Settings for fetching the OPA policy from - an external registry. Use it alternatively to ''inlineRego''. + 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 From 3b12430f9a3773342c3b35449a1eeea57d2400d5 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 14:41:40 +0200 Subject: [PATCH 23/35] docs: update for v1beta2: user guides | User guide | File | |----------------------------------------------------------------------------------------|----------------------------------------------------------------------------| | Anonymous access | `docs/user-guides/anonymous-access.md` | | Authentication with API keys | `docs/user-guides/api-key-authentication.md` | | Authenticated rate limiting (with Envoy Dynamic Metadata) | `docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md` | | Integration with Authzed/SpiceDB | `docs/user-guides/authzed.md` | | Caching | `docs/user-guides/caching.md` | | Redirecting to a login page | `docs/user-guides/deny-with-redirect-to-login.md` | | Edge Authentication Architecture (EAA) | `docs/user-guides/edge-authentication-architecture-festival-wristbands.md` | | Mixing Envoy built-in filter for auth and Authorino | `docs/user-guides/envoy-jwt-authn-and-authorino.md` | | Fetching auth metadata from external sources | `docs/user-guides/external-metadata.md` | | Host override via context extension | `docs/user-guides/host-override.md` | | HTTP "Basic" Authentication (RFC 7235) | `docs/user-guides/http-basic-authentication.md` | | Injecting data in the request | `docs/user-guides/injecting-data.md` | | Simple pattern-matching authorization policies | `docs/user-guides/json-pattern-matching-authorization.md` | | Authorization with Keycloak Authorization Services | `docs/user-guides/keycloak-authorization-services.md` | | Kubernetes RBAC for service authorization (SubjectAccessReview API) | `docs/user-guides/kubernetes-subjectaccessreview.md` | | Authentication with Kubernetes tokens (TokenReview API) | `docs/user-guides/kubernetes-tokenreview.md` | | Authentication with X.509 certificates and Mutual Transport Layer Security (mTLS) | `docs/user-guides/mtls-authentication.md` | | OAuth 2.0 token introspection (RFC 7662) | `docs/user-guides/oauth2-token-introspection.md` | | OpenID Connect Discovery and authentication with JWTs | `docs/user-guides/oidc-jwt-authentication.md` | | OpenID Connect (OIDC) and Role-Based Access Control (RBAC) with Authorino and Keycloak | `docs/user-guides/oidc-rbac.md` | | OpenID Connect UserInfo | `docs/user-guides/oidc-user-info.md` | | Open Policy Agent (OPA) Rego policies | `docs/user-guides/opa-authorization.md` | | Passing credentials (`Authorization` header, cookie headers and others) | `docs/user-guides/passing-credentials.md` | | Resource-level authorization with User-Managed Access (UMA) resource registry | `docs/user-guides/resource-level-authorization-uma.md` | | Reducing the operational space | `docs/user-guides/sharding.md` | | Token normalization | `docs/user-guides/token-normalization.md` | | Using Authorino as ValidatingWebhook service | `docs/user-guides/validating-webhook.md` | --- docs/user-guides/anonymous-access.md | 8 +- docs/user-guides/api-key-authentication.md | 20 +- ...ed-rate-limiting-envoy-dynamic-metadata.md | 36 +-- docs/user-guides/authzed.md | 61 +++-- docs/user-guides/caching.md | 59 ++--- .../deny-with-redirect-to-login.md | 66 +++-- ...cation-architecture-festival-wristbands.md | 76 +++--- .../envoy-jwt-authn-and-authorino.md | 53 ++-- docs/user-guides/external-metadata.md | 49 ++-- docs/user-guides/host-override.md | 12 +- docs/user-guides/http-basic-authentication.md | 38 +-- docs/user-guides/injecting-data.md | 42 ++-- .../json-pattern-matching-authorization.md | 30 +-- .../keycloak-authorization-services.md | 74 +++--- .../kubernetes-subjectaccessreview.md | 18 +- docs/user-guides/kubernetes-tokenreview.md | 12 +- docs/user-guides/mtls-authentication.md | 26 +- .../user-guides/oauth2-token-introspection.md | 46 ++-- docs/user-guides/oidc-jwt-authentication.md | 10 +- docs/user-guides/oidc-rbac.md | 78 +++--- docs/user-guides/oidc-user-info.md | 28 +-- docs/user-guides/opa-authorization.md | 36 +-- docs/user-guides/passing-credentials.md | 78 +++--- .../resource-level-authorization-uma.md | 105 ++++---- docs/user-guides/sharding.md | 32 +-- docs/user-guides/token-normalization.md | 59 +++-- docs/user-guides/validating-webhook.md | 238 +++++++++--------- 27 files changed, 688 insertions(+), 702 deletions(-) diff --git a/docs/user-guides/anonymous-access.md b/docs/user-guides/anonymous-access.md index 65c7dff9..b7446eef 100644 --- a/docs/user-guides/anonymous-access.md +++ b/docs/user-guides/anonymous-access.md @@ -79,16 +79,16 @@ kubectl port-forward deployment/envoy 8000:8000 & ```sh kubectl apply -f -< 0; not forbidden } - allValues: true - - - name: apikey-authn-requires-k8s-role-binding - priority: 1 - when: - - selector: auth.authorization.features.apiKey - operator: eq - value: "true" - kubernetes: - user: - valueFrom: { authJSON: auth.identity.username } - resourceAttributes: - namespace: { value: authorino } - group: { value: authorino.kuadrant.io } - resource: { value: authconfigs-with-apikeys } - verb: { value: create } - - - name: metadata-cache-ttl - priority: 1 - opa: - inlineRego: | - invalid_ttl = input.auth.authorization.features.authconfig.spec.metadata[_].cache.ttl != 300 - allow { not invalid_ttl } + "features": + opa: + rego: | + authconfig = json.unmarshal(input.context.request.http.body).request.object + + forbidden { count(object.get(authconfig.spec, "authentication", [])) == 0 } + forbidden { authconfig.spec.authentication[_].anonymous } + forbidden { authconfig.spec.authentication[_].kubernetesTokenReview } + forbidden { authconfig.spec.authentication[_].plain } + forbidden { authconfig.spec.authorization[_].kubernetesSubjectAccessReview } + forbidden { authconfig.spec.response.success.headers[_].wristband } + + apiKey { authconfig.spec.authentication[_].apiKey } + + allow { count(authconfig.spec.authentication) > 0; not forbidden } + allValues: true + + "apikey-authn-requires-k8s-role-binding": + priority: 1 + when: + - selector: auth.authorization.features.apiKey + operator: eq + value: "true" + kubernetesSubjectAccessReview: + user: + selector: auth.identity.username + resourceAttributes: + namespace: { value: authorino } + group: { value: authorino.kuadrant.io } + resource: { value: authconfigs-with-apikeys } + verb: { value: create } + + "metadata-cache-ttl": + priority: 1 + opa: + rego: | + invalid_ttl = input.auth.authorization.features.authconfig.spec.metadata[_].cache.ttl != 300 + allow { not invalid_ttl } EOF ``` @@ -237,17 +237,17 @@ kubectl create namespace myapp ```sh kubectl -n myapp apply -f -< Date: Tue, 5 Sep 2023 15:11:11 +0200 Subject: [PATCH 24/35] docs: fix broken links to feature description --- docs/user-guides/anonymous-access.md | 4 ++-- docs/user-guides/api-key-authentication.md | 2 +- ...thenticated-rate-limiting-envoy-dynamic-metadata.md | 6 +++--- docs/user-guides/authzed.md | 4 ++-- docs/user-guides/caching.md | 4 ++-- docs/user-guides/deny-with-redirect-to-login.md | 8 ++++---- ...-authentication-architecture-festival-wristbands.md | 8 ++++---- docs/user-guides/envoy-jwt-authn-and-authorino.md | 6 +++--- docs/user-guides/external-metadata.md | 4 ++-- docs/user-guides/http-basic-authentication.md | 6 +++--- docs/user-guides/injecting-data.md | 6 +++--- .../user-guides/json-pattern-matching-authorization.md | 6 +++--- docs/user-guides/keycloak-authorization-services.md | 2 +- docs/user-guides/kubernetes-subjectaccessreview.md | 6 +++--- docs/user-guides/kubernetes-tokenreview.md | 2 +- docs/user-guides/mtls-authentication.md | 4 ++-- docs/user-guides/oauth2-token-introspection.md | 4 ++-- docs/user-guides/oidc-jwt-authentication.md | 2 +- docs/user-guides/oidc-rbac.md | 4 ++-- docs/user-guides/oidc-user-info.md | 4 ++-- docs/user-guides/opa-authorization.md | 2 +- docs/user-guides/passing-credentials.md | 4 ++-- docs/user-guides/resource-level-authorization-uma.md | 2 +- docs/user-guides/sharding.md | 2 +- docs/user-guides/token-normalization.md | 10 +++++----- docs/user-guides/validating-webhook.md | 10 +++++----- 26 files changed, 61 insertions(+), 61 deletions(-) diff --git a/docs/user-guides/anonymous-access.md b/docs/user-guides/anonymous-access.md index b7446eef..dfd70762 100644 --- a/docs/user-guides/anonymous-access.md +++ b/docs/user-guides/anonymous-access.md @@ -6,7 +6,7 @@ Bypass identity verification or fall back to anonymous access when credentials f Authorino features in this guide: @@ -96,7 +96,7 @@ The example above enables anonymous access (i.e. removes authentication), withou For more sophisticated use cases of anonymous access with Authorino, consider combining this feature with other identity sources in the `AuthConfig` while playing with the [priorities](./../features.md#common-feature-priorities) of each source, as well as combination with `when` [conditions](./../features.md#common-feature-conditions-when), and/or adding authorization policies that either cover authentication or address anonymous access with proper rules (e.g. enforcing read-only access). -Check out the docs for the [Anonymous access](./../features.md#anonymous-access-identityanonymous) feature for an example of an `AuthConfig` that falls back to anonymous access when a priority OIDC/JWT-based authentication fails, and enforces a read-only policy in such cases. +Check out the docs for the [Anonymous access](./../features.md#anonymous-access-authenticationanonymous) feature for an example of an `AuthConfig` that falls back to anonymous access when a priority OIDC/JWT-based authentication fails, and enforces a read-only policy in such cases. ## 6. Consume the API diff --git a/docs/user-guides/api-key-authentication.md b/docs/user-guides/api-key-authentication.md index 866b50fb..791f7fbb 100644 --- a/docs/user-guides/api-key-authentication.md +++ b/docs/user-guides/api-key-authentication.md @@ -6,7 +6,7 @@ Issue API keys stored in Kubernetes `Secret`s for clients to authenticate with y Authorino features in this guide:
    -
  • Identity verification & authentication → API key
  • +
  • Identity verification & authentication → API key
diff --git a/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md b/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md index b9205823..e2160e1b 100644 --- a/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md +++ b/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md @@ -7,8 +7,8 @@ Provide Envoy with dynamic metadata about the external authorization process to Authorino features in this guide: @@ -125,7 +125,7 @@ EOF An annotation `auth-data/username` will be read from the Kubernetes `Secret`s storing valid API keys and passed as dynamic metadata `{ "ext_auth_data": { "username": «annotations.auth-data/username» } }`. -Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-valuefromauthjson) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json). +Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-selector) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json). ## 7. Create a couple of API keys diff --git a/docs/user-guides/authzed.md b/docs/user-guides/authzed.md index 77b971b0..262f1358 100644 --- a/docs/user-guides/authzed.md +++ b/docs/user-guides/authzed.md @@ -6,8 +6,8 @@ Permission requests sent to a Google Zanzibar-based [Authzed/SpiceDB](https://au Authorino features in this guide:
diff --git a/docs/user-guides/caching.md b/docs/user-guides/caching.md index fecdd3b7..9db1779f 100644 --- a/docs/user-guides/caching.md +++ b/docs/user-guides/caching.md @@ -20,10 +20,10 @@ Cases where one will **NOT** want to enable caching, due to relatively cheap com Authorino features in this guide: diff --git a/docs/user-guides/deny-with-redirect-to-login.md b/docs/user-guides/deny-with-redirect-to-login.md index c487c0c3..3c5d85b5 100644 --- a/docs/user-guides/deny-with-redirect-to-login.md +++ b/docs/user-guides/deny-with-redirect-to-login.md @@ -6,9 +6,9 @@ Customize response status code and headers on failed requests to redirect users Authorino features in this guide: @@ -118,7 +118,7 @@ spec: EOF ``` -Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-valuefromauthjson) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json). +Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-selector) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json). ## 6. Create an API key diff --git a/docs/user-guides/edge-authentication-architecture-festival-wristbands.md b/docs/user-guides/edge-authentication-architecture-festival-wristbands.md index 0b8d8b54..24e3d804 100644 --- a/docs/user-guides/edge-authentication-architecture-festival-wristbands.md +++ b/docs/user-guides/edge-authentication-architecture-festival-wristbands.md @@ -14,10 +14,10 @@ As a minimum, EAA allows to simplify authentication between applications and mic Authorino features in this guide: diff --git a/docs/user-guides/envoy-jwt-authn-and-authorino.md b/docs/user-guides/envoy-jwt-authn-and-authorino.md index 2c8730a0..3e84eabf 100644 --- a/docs/user-guides/envoy-jwt-authn-and-authorino.md +++ b/docs/user-guides/envoy-jwt-authn-and-authorino.md @@ -12,10 +12,10 @@ All requests to the Talker API will be authenticated in Envoy. However, requests Authorino features in this guide: diff --git a/docs/user-guides/external-metadata.md b/docs/user-guides/external-metadata.md index 618b8eb6..8a8a0ba9 100644 --- a/docs/user-guides/external-metadata.md +++ b/docs/user-guides/external-metadata.md @@ -7,7 +7,7 @@ Get online data from remote HTTP services to enhance authorization rules. Authorino features in this guide: @@ -128,7 +128,7 @@ spec: EOF ``` -Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-valuefromauthjson) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json), including the description of the `@extract` string modifier. +Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-selector) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json), including the description of the `@extract` string modifier. ## 6. Create an API key diff --git a/docs/user-guides/http-basic-authentication.md b/docs/user-guides/http-basic-authentication.md index 61211765..415bcca9 100644 --- a/docs/user-guides/http-basic-authentication.md +++ b/docs/user-guides/http-basic-authentication.md @@ -6,8 +6,8 @@ Turn Authorino API key `Secret`s settings into HTTP basic auth. Authorino features in this guide: @@ -118,7 +118,7 @@ EOF The config specifies an Access Control List (ACL), by which only the user `john` is authorized to consume the `/bye` endpoint of the API. -Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-valuefromauthjson) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json), including the description of the string modifiers `@extract` and `@case` used above. Check out as well the common feature [Conditions](./../features.md#common-feature-conditions-when) about skipping parts of an `AuthConfig` in the auth pipeline based on context. +Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-selector) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json), including the description of the string modifiers `@extract` and `@case` used above. Check out as well the common feature [Conditions](./../features.md#common-feature-conditions-when) about skipping parts of an `AuthConfig` in the auth pipeline based on context. ## 6. Create user credentials diff --git a/docs/user-guides/injecting-data.md b/docs/user-guides/injecting-data.md index 95dcc7d7..bb4a8ac1 100644 --- a/docs/user-guides/injecting-data.md +++ b/docs/user-guides/injecting-data.md @@ -6,8 +6,8 @@ Inject HTTP headers with serialized JSON content. Authorino features in this guide: @@ -120,7 +120,7 @@ spec: EOF ``` -Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-valuefromauthjson) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json). +Check out the docs for information about the common feature [JSON paths](./../features.md#common-feature-json-paths-selector) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json). ## 6. Create an API key diff --git a/docs/user-guides/json-pattern-matching-authorization.md b/docs/user-guides/json-pattern-matching-authorization.md index 2fb00eb5..bdeefca1 100644 --- a/docs/user-guides/json-pattern-matching-authorization.md +++ b/docs/user-guides/json-pattern-matching-authorization.md @@ -6,8 +6,8 @@ Write simple authorization rules based on JSON patterns matched against Authorin Authorino features in this guide: @@ -130,7 +130,7 @@ spec: EOF ``` -Check out the docs for information about semantics and operators supported by the [JSON pattern-matching authorization](./../features.md#json-pattern-matching-authorization-rules-authorizationjson) feature, as well the common feature [JSON paths](./../features.md#common-feature-json-paths-valuefromauthjson) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json), including the description of the string modifier `@extract` used above. Check out as well the common feature [Conditions](./../features.md#common-feature-conditions-when) about skipping parts of an `AuthConfig` in the auth pipeline based on context. +Check out the docs for information about semantics and operators supported by the [JSON pattern-matching authorization](./../features.md#pattern-matching-authorization-authorizationpatternmatching) feature, as well the common feature [JSON paths](./../features.md#common-feature-json-paths-selector) for reading from the [Authorization JSON](./../architecture.md#the-authorization-json), including the description of the string modifier `@extract` used above. Check out as well the common feature [Conditions](./../features.md#common-feature-conditions-when) about skipping parts of an `AuthConfig` in the auth pipeline based on context. ## 6. Obtain an access token and consume the API diff --git a/docs/user-guides/keycloak-authorization-services.md b/docs/user-guides/keycloak-authorization-services.md index dea2b133..41562a1e 100644 --- a/docs/user-guides/keycloak-authorization-services.md +++ b/docs/user-guides/keycloak-authorization-services.md @@ -8,7 +8,7 @@ This user guide is an example of how to use Authorino as an adapter to Keycloak Authorino features in this guide: diff --git a/docs/user-guides/kubernetes-subjectaccessreview.md b/docs/user-guides/kubernetes-subjectaccessreview.md index 41d6bc0a..c74baa72 100644 --- a/docs/user-guides/kubernetes-subjectaccessreview.md +++ b/docs/user-guides/kubernetes-subjectaccessreview.md @@ -6,8 +6,8 @@ Manage permissions in the Kubernetes RBAC and let Authorino to check them in req Authorino features in this guide: @@ -108,7 +108,7 @@ spec: EOF ``` -Check out the [spec](./../features.md#kubernetes-subjectaccessreview-authorizationkubernetes) for the Authorino Kubernetes SubjectAccessReview authorization feature, for resource attributes permission checks where SubjectAccessReviews issued by Authorino are modeled in terms of common attributes of operations on Kubernetes resources (namespace, API group, kind, name, subresource, verb). +Check out the [spec](./../features.md#kubernetes-subjectaccessreview-authorizationkubernetessubjectaccessreview) for the Authorino Kubernetes SubjectAccessReview authorization feature, for resource attributes permission checks where SubjectAccessReviews issued by Authorino are modeled in terms of common attributes of operations on Kubernetes resources (namespace, API group, kind, name, subresource, verb). ## 6. Create roles associated with endpoints of the API diff --git a/docs/user-guides/kubernetes-tokenreview.md b/docs/user-guides/kubernetes-tokenreview.md index b6616655..8e69907c 100644 --- a/docs/user-guides/kubernetes-tokenreview.md +++ b/docs/user-guides/kubernetes-tokenreview.md @@ -6,7 +6,7 @@ Validate Kubernetes Service Account tokens to authenticate requests to your prot Authorino features in this guide: diff --git a/docs/user-guides/mtls-authentication.md b/docs/user-guides/mtls-authentication.md index 5ece6837..797ab342 100644 --- a/docs/user-guides/mtls-authentication.md +++ b/docs/user-guides/mtls-authentication.md @@ -6,8 +6,8 @@ Verify client X.509 certificates against trusted root CAs stored in Kubernetes ` Authorino features in this guide: diff --git a/docs/user-guides/oauth2-token-introspection.md b/docs/user-guides/oauth2-token-introspection.md index 8e230972..7f11463e 100644 --- a/docs/user-guides/oauth2-token-introspection.md +++ b/docs/user-guides/oauth2-token-introspection.md @@ -6,8 +6,8 @@ Introspect OAuth 2.0 access tokens (e.g. opaque tokens) for online user data and Authorino features in this guide: diff --git a/docs/user-guides/oidc-jwt-authentication.md b/docs/user-guides/oidc-jwt-authentication.md index 78c1f283..d973c42f 100644 --- a/docs/user-guides/oidc-jwt-authentication.md +++ b/docs/user-guides/oidc-jwt-authentication.md @@ -6,7 +6,7 @@ Validate JSON Web Tokens (JWT) issued and signed by an OpenID Connect server; le Authorino features in this guide: diff --git a/docs/user-guides/oidc-rbac.md b/docs/user-guides/oidc-rbac.md index 7d07d834..b3d5f081 100644 --- a/docs/user-guides/oidc-rbac.md +++ b/docs/user-guides/oidc-rbac.md @@ -8,8 +8,8 @@ In this user guide, you will learn via example how to implement a simple Role-Ba Authorino features in this guide: diff --git a/docs/user-guides/oidc-user-info.md b/docs/user-guides/oidc-user-info.md index 9d6c779d..e2527cee 100644 --- a/docs/user-guides/oidc-user-info.md +++ b/docs/user-guides/oidc-user-info.md @@ -7,8 +7,8 @@ Fetch user info for OpenID Connect ID tokens in request-time for extra metadata Authorino features in this guide: diff --git a/docs/user-guides/opa-authorization.md b/docs/user-guides/opa-authorization.md index b735c6f9..3b0f6144 100644 --- a/docs/user-guides/opa-authorization.md +++ b/docs/user-guides/opa-authorization.md @@ -7,7 +7,7 @@ Leverage the power of Open Policy Agent (OPA) policies, evaluated against Author Authorino features in this guide: diff --git a/docs/user-guides/passing-credentials.md b/docs/user-guides/passing-credentials.md index 99b32171..abdcb98d 100644 --- a/docs/user-guides/passing-credentials.md +++ b/docs/user-guides/passing-credentials.md @@ -6,8 +6,8 @@ Customize where credentials are supplied in the request by each trusted source o Authorino features in this guide:
    -
  • Identity verification & authentication → Auth credentials
  • -
  • Identity verification & authentication → API key
  • +
  • Identity verification & authentication → Auth credentials
  • +
  • Identity verification & authentication → API key
diff --git a/docs/user-guides/resource-level-authorization-uma.md b/docs/user-guides/resource-level-authorization-uma.md index 8f60868a..a3f9d3f2 100644 --- a/docs/user-guides/resource-level-authorization-uma.md +++ b/docs/user-guides/resource-level-authorization-uma.md @@ -7,7 +7,7 @@ Fetch resource metadata relevant for your authorization policies from Keycloak a Authorino features in this guide: diff --git a/docs/user-guides/sharding.md b/docs/user-guides/sharding.md index 5dc7bbb5..10f49941 100644 --- a/docs/user-guides/sharding.md +++ b/docs/user-guides/sharding.md @@ -11,7 +11,7 @@ By default, Authorino will watch events related to all `AuthConfig` custom resou Authorino features in this guide:
  • Sharding
  • -
  • Identity verification & authentication → API key
  • +
  • Identity verification & authentication → API key
diff --git a/docs/user-guides/token-normalization.md b/docs/user-guides/token-normalization.md index 6ff42d9f..8d3fbe0b 100644 --- a/docs/user-guides/token-normalization.md +++ b/docs/user-guides/token-normalization.md @@ -6,16 +6,16 @@ The most typical use-case for token normalization involves accepting tokens issu This user guide focuses on the aspect of mutation of the identity claims resolved from an authentication token, to a certain data format and/or by extending them, so that required attributes can thereafter be trusted to be present among the claims, in a desired form. For such, Authorino allows to extend resolved identity objects with custom attributes (custom claims) of either static values or with values fetched from the [Authorization JSON](./../architecture.md#the-authorization-json). -For not only normalizing the identity claims for purpose of writing simpler authorization checks and policies, but also getting Authorino to issue a new token in a normalized format, check the [Festival Wristband tokens](./../features.md#festival-wristband-tokens-responsewristband) feature. +For not only normalizing the identity claims for purpose of writing simpler authorization checks and policies, but also getting Authorino to issue a new token in a normalized format, check the [Festival Wristband tokens](./../features.md#festival-wristband-tokens-responsesuccessheadersdynamicmetadatawristband) feature.
Authorino features in this guide: diff --git a/docs/user-guides/validating-webhook.md b/docs/user-guides/validating-webhook.md index d24f9cd2..f65e3565 100644 --- a/docs/user-guides/validating-webhook.md +++ b/docs/user-guides/validating-webhook.md @@ -23,13 +23,13 @@ For convenience, the same instance of Authorino used to enforce the AuthConfig a Authorino features in this guide: From 278acb91cac757dce75f9b338a3c06ed2a9e1209 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 18:53:02 +0200 Subject: [PATCH 25/35] fix: conversion of v1beta2.ValueOrSelector type missing to parse string and RawExtension back and forth as json --- api/v1beta2/auth_config_conversion.go | 7 +++++-- api/v1beta2/auth_config_conversion_test.go | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go index 308c6bd2..55cae549 100644 --- a/api/v1beta2/auth_config_conversion.go +++ b/api/v1beta2/auth_config_conversion.go @@ -1,9 +1,12 @@ package v1beta2 import ( + "fmt" + "github.com/kuadrant/authorino/api/v1beta1" "github.com/kuadrant/authorino/pkg/utils" + "github.com/tidwall/gjson" k8sruntime "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/conversion" @@ -252,7 +255,7 @@ func convertEvaluatorCachingFrom(src *v1beta1.EvaluatorCaching) *EvaluatorCachin func convertValueOrSelectorTo(src ValueOrSelector) v1beta1.StaticOrDynamicValue { return v1beta1.StaticOrDynamicValue{ - Value: string(src.Value.Raw), + Value: gjson.ParseBytes(src.Value.Raw).String(), ValueFrom: convertSelectorTo(src), } } @@ -260,7 +263,7 @@ func convertValueOrSelectorTo(src ValueOrSelector) v1beta1.StaticOrDynamicValue func convertValueOrSelectorFrom(src v1beta1.StaticOrDynamicValue) ValueOrSelector { value := k8sruntime.RawExtension{} if src.ValueFrom.AuthJSON == "" { - value.Raw = []byte(src.Value) + value.Raw = []byte(fmt.Sprintf(`"%s"`, src.Value)) } return ValueOrSelector{ Value: value, diff --git a/api/v1beta2/auth_config_conversion_test.go b/api/v1beta2/auth_config_conversion_test.go index 45d68c3f..0f6d95b1 100644 --- a/api/v1beta2/auth_config_conversion_test.go +++ b/api/v1beta2/auth_config_conversion_test.go @@ -510,7 +510,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { }, "resource": { "kind": { - "value": "\"blog/post\"", + "value": "blog/post", "valueFrom": {} }, "name": { @@ -525,7 +525,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { }, "subject": { "kind": { - "value": "\"blog/user\"", + "value": "blog/user", "valueFrom": {} }, "name": { @@ -637,7 +637,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { "denyWith": { "unauthenticated": { "message": { - "value": "\"Authentication failed\"", + "value": "Authentication failed", "valueFrom": {} } }, @@ -651,7 +651,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { } ], "message": { - "value": "\"Access denied\"", + "value": "Access denied", "valueFrom": {} } } @@ -967,7 +967,7 @@ func hubAuthConfig() *v1beta1.AuthConfig { "metrics": false, "name": "x-auth-service", "plain": { - "value": "\"Authorino\"", + "value": "Authorino", "valueFrom": {} }, "priority": 0, From 07a2a2f9efd14a7af49e5638cb08f0a50f4c16a3 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 5 Sep 2023 19:17:24 +0200 Subject: [PATCH 26/35] docs: fixup: 'Using Authorino as ValidatingWebhook service' user guide --- docs/user-guides/validating-webhook.md | 52 +++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/user-guides/validating-webhook.md b/docs/user-guides/validating-webhook.md index f65e3565..c82ee717 100644 --- a/docs/user-guides/validating-webhook.md +++ b/docs/user-guides/validating-webhook.md @@ -216,7 +216,7 @@ webhooks: path: /check rules: - apiGroups: ["authorino.kuadrant.io"] - apiVersions: ["v1beta1"] + apiVersions: ["v1beta2"] resources: ["authconfigs"] operations: ["CREATE", "UPDATE"] scope: Namespaced @@ -245,7 +245,7 @@ spec: hosts: - myapp.io authentication: - "keycloak" + "keycloak": jwt: issuerUrl: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant EOF @@ -267,11 +267,11 @@ spec: - myapp.io EOF # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"identity":null}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":null}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized ``` ```sh @@ -288,11 +288,11 @@ spec: anonymous: {} EOF # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"],\"identity\":[{\"anonymous\":{},\"name\":\"anonymous-access\"}]}}\n"}},"spec":{"identity":[{"anonymous":{},"name":"anonymous-access"}]}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"anonymous-access\":{\"anonymous\":{}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"anonymous-access":{"anonymous":{}},"keycloak":null}}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized ``` Kubernetes TokenReview: @@ -312,11 +312,11 @@ spec: audiences: ["myapp"] EOF # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"],\"identity\":[{\"kubernetes\":{\"audiences\":[\"myapp\"]},\"name\":\"k8s-tokenreview\"}]}}\n"}},"spec":{"identity":[{"kubernetes":{"audiences":["myapp"]},"name":"k8s-tokenreview"}]}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"k8s-tokenreview\":{\"kubernetesTokenReview\":{\"audiences\":[\"myapp\"]}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"k8s-tokenreview":{"kubernetesTokenReview":{"audiences":["myapp"]}},"keycloak":null}}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized ``` Plain identity extracted from context: @@ -336,11 +336,11 @@ spec: selector: context.metadata_context.filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt EOF # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"],\"identity\":[{\"name\":\"envoy-jwt-authn\",\"plain\":{\"authJSON\":\"context.metadata_context.filter_metadata.envoy\\\\.filters\\\\.http\\\\.jwt_authn|verified_jwt\"}}]}}\n"}},"spec":{"identity":[{"name":"envoy-jwt-authn","plain":{"authJSON":"context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt"}}]}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"envoy-jwt-authn\":{\"plain\":{\"selector\":\"context.metadata_context.filter_metadata.envoy\\\\.filters\\\\.http\\\\.jwt_authn|verified_jwt\"}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"envoy-jwt-authn":{"plain":{"selector":"context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt"}},"keycloak":null}}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized ``` Kubernetes SubjectAccessReview: @@ -365,11 +365,11 @@ spec: selector: auth.identity.sub EOF # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authorization\":[{\"kubernetes\":{\"user\":{\"valueFrom\":{\"authJSON\":\"auth.identity.sub\"}}},\"name\":\"k8s-subjectaccessreview\"}],\"hosts\":[\"myapp.io\"],\"identity\":[{\"name\":\"keycloak\",\"oidc\":{\"endpoint\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}]}}\n"}},"spec":{"authorization":[{"kubernetes":{"user":{"valueFrom":{"authJSON":"auth.identity.sub"}}},"name":"k8s-subjectaccessreview"}],"identity":[{"name":"keycloak","oidc":{"endpoint":"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant"}}]}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"keycloak\":{\"jwt\":{\"issuerUrl\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}},\"authorization\":{\"k8s-subjectaccessreview\":{\"kubernetesSubjectAccessReview\":{\"user\":{\"selector\":\"auth.identity.sub\"}}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authorization":{"k8s-subjectaccessreview":{"kubernetesSubjectAccessReview":{"user":{"selector":"auth.identity.sub"}}}}}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized ``` Festival Wristband tokens: @@ -412,11 +412,11 @@ spec: EOF # secret/wristband-signing-key created # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"],\"identity\":[{\"name\":\"keycloak\",\"oidc\":{\"endpoint\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}],\"response\":[{\"name\":\"wristband\",\"wristband\":{\"issuer\":\"http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/myapp/myapp-protection/wristband\",\"signingKeyRefs\":[{\"algorithm\":\"ES256\",\"name\":\"wristband-signing-key\"}]}}]}}\n"}},"spec":{"identity":[{"name":"keycloak","oidc":{"endpoint":"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant"}}],"response":[{"name":"wristband","wristband":{"issuer":"http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/myapp/myapp-protection/wristband","signingKeyRefs":[{"algorithm":"ES256","name":"wristband-signing-key"}]}}]}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"keycloak\":{\"jwt\":{\"issuerUrl\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}},\"hosts\":[\"myapp.io\"],\"response\":{\"success\":{\"headers\":{\"wristband\":{\"wristband\":{\"issuer\":\"http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/myapp/myapp-protection/wristband\",\"signingKeyRefs\":[{\"algorithm\":\"ES256\",\"name\":\"wristband-signing-key\"}]}}}}}}}\n"}},"spec":{"response":{"success":{"headers":{"wristband":{"wristband":{"issuer":"http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/myapp/myapp-protection/wristband","signingKeyRefs":[{"algorithm":"ES256","name":"wristband-signing-key"}]}}}}}}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized ``` ### With features that require additional permissions @@ -439,11 +439,11 @@ spec: matchLabels: { app: myapp } EOF # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"],\"identity\":[{\"apiKey\":{\"selector\":{\"matchLabels\":{\"app\":\"myapp\"}}},\"name\":\"api-key\"}]}}\n"}},"spec":{"identity":[{"apiKey":{"selector":{"matchLabels":{"app":"myapp"}}},"name":"api-key"}]}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"api-key\":{\"apiKey\":{\"selector\":{\"matchLabels\":{\"app\":\"myapp\"}}}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"api-key":{"apiKey":{"selector":{"matchLabels":{"app":"myapp"}}}},"keycloak":null}}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Not authorized: unknown reason +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Not authorized: unknown reason ``` Add the required permissions: @@ -511,11 +511,11 @@ spec: ttl: 60 EOF # Error from server: error when applying patch: -# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"],\"identity\":[{\"name\":\"keycloak\",\"oidc\":{\"endpoint\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}],\"metadata\":[{\"cache\":{\"key\":{\"value\":\"global\"},\"ttl\":60},\"http\":{\"endpoint\":\"http://metadata.io\",\"method\":\"GET\"},\"name\":\"external-source\"}]}}\n"}},"spec":{"identity":[{"name":"keycloak","oidc":{"endpoint":"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant"}}],"metadata":[{"cache":{"key":{"value":"global"},"ttl":60},"http":{"endpoint":"http://metadata.io","method":"GET"},"name":"external-source"}]}} +# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"keycloak\":{\"jwt\":{\"issuerUrl\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}},\"hosts\":[\"myapp.io\"],\"metadata\":{\"external-source\":{\"cache\":{\"key\":{\"value\":\"global\"},\"ttl\":60},\"http\":{\"url\":\"http://metadata.io\"}}}}}\n"}},"spec":{"authentication":{"api-key":null,"keycloak":{"jwt":{"issuerUrl":"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant"}}},"metadata":{"external-source":{"cache":{"key":{"value":"global"},"ttl":60},"http":{"url":"http://metadata.io"}}}}} # to: -# Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig" +# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig" # Name: "myapp-protection", Namespace: "myapp" -# for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized +# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized ``` Valid: From c4416d192be45da92de8a38e60de481c8488e3da Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 6 Sep 2023 11:57:42 +0200 Subject: [PATCH 27/35] fix: set default http header custom response wrapper when converting to authconfig v1beta1 --- api/v1beta2/auth_config_conversion.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1beta2/auth_config_conversion.go b/api/v1beta2/auth_config_conversion.go index 55cae549..d0acb1de 100644 --- a/api/v1beta2/auth_config_conversion.go +++ b/api/v1beta2/auth_config_conversion.go @@ -159,7 +159,7 @@ func (dst *AuthConfig) ConvertFrom(srcRaw conversion.Hub) error { } for _, responseSrc := range src.Spec.Response { - if responseSrc.Wrapper != "httpHeader" { + if responseSrc.Wrapper != "httpHeader" && responseSrc.Wrapper != "" { continue } if dst.Spec.Response.Success.Headers == nil { From bb893446262d50d4b3831ee557f2ace990faa682 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 6 Sep 2023 12:25:41 +0200 Subject: [PATCH 28/35] e2e tests for v1beta2 --- .github/workflows/e2e-test.yaml | 2 + .github/workflows/integration-test.yaml | 4 +- Makefile | 3 +- tests/e2e-test.sh | 5 +- tests/{ => v1beta1}/authconfig-invalid.yaml | 0 tests/{ => v1beta1}/authconfig.yaml | 0 tests/v1beta2/authconfig-invalid.yaml | 30 ++ tests/v1beta2/authconfig.yaml | 308 ++++++++++++++++++++ 8 files changed, 347 insertions(+), 5 deletions(-) rename tests/{ => v1beta1}/authconfig-invalid.yaml (100%) rename tests/{ => v1beta1}/authconfig.yaml (100%) create mode 100644 tests/v1beta2/authconfig-invalid.yaml create mode 100644 tests/v1beta2/authconfig.yaml diff --git a/.github/workflows/e2e-test.yaml b/.github/workflows/e2e-test.yaml index 37a3e523..5d8987e4 100644 --- a/.github/workflows/e2e-test.yaml +++ b/.github/workflows/e2e-test.yaml @@ -15,6 +15,7 @@ jobs: matrix: go-version: [1.19.x] platform: [ubuntu-latest] + authconfig_version: [v1beta1, v1beta2] runs-on: ${{ matrix.platform }} defaults: run: @@ -34,5 +35,6 @@ jobs: - name: Run make e2e env: OPERATOR_VERSION: ${{ github.event.inputs.operatorVersion }} + AUTHCONFIG_VERSION: ${{ matrix.authconfig_version }} run: | make e2e diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 68efc425..1cd933f1 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -79,7 +79,7 @@ jobs: - name: Run e2e tests env: NAMESPACE: authorino - AUTHCONFIG: https://raw.githubusercontent.com/Kuadrant/authorino/main/tests/authconfig.yaml - AUTHCONFIG_INVALID: https://raw.githubusercontent.com/Kuadrant/authorino/main/tests/authconfig-invalid.yaml + AUTHCONFIG: https://raw.githubusercontent.com/Kuadrant/authorino/main/tests/v1beta2/authconfig.yaml + AUTHCONFIG_INVALID: https://raw.githubusercontent.com/Kuadrant/authorino/main/tests/v1beta2/authconfig-invalid.yaml run: | curl -sSL https://raw.githubusercontent.com/Kuadrant/authorino/main/tests/e2e-test.sh | bash diff --git a/Makefile b/Makefile index 47ef9ed8..c2f62680 100644 --- a/Makefile +++ b/Makefile @@ -144,10 +144,11 @@ report-benchmarks: cover: ## Shows test coverage go tool cover -html=cover.out +AUTHCONFIG_VERSION ?= v1beta2 VERBOSE ?= 0 e2e: ## Runs the end-to-end tests on a local environment setup $(MAKE) local-setup NAMESPACE=authorino KIND_CLUSTER_NAME=authorino-e2e AUTHORINO_IMAGE=$(AUTHORINO_IMAGE) TLS_ENABLED=$(TLS_ENABLED) OPERATOR_BRANCH=$(OPERATOR_BRANCH) AUTHORINO_MANIFESTS=$(AUTHORINO_MANIFESTS) AUTHORINO_INSTANCE=$(AUTHORINO_INSTANCE) ENVOY_OVERLAY=$(ENVOY_OVERLAY) DEPLOY_KEYCLOAK=1 FF=1 - NAMESPACE=authorino VERBOSE=$(VERBOSE) ./tests/e2e-test.sh + NAMESPACE=authorino AUTHCONFIG_VERSION=$(AUTHCONFIG_VERSION) VERBOSE=$(VERBOSE) ./tests/e2e-test.sh ##@ Apps diff --git a/tests/e2e-test.sh b/tests/e2e-test.sh index 87072ff4..503beaef 100755 --- a/tests/e2e-test.sh +++ b/tests/e2e-test.sh @@ -8,8 +8,9 @@ for cmd in realpath kubectl curl jq base64; do done namespace=${NAMESPACE:-"authorino"} -authconfig=${AUTHCONFIG:-"$(dirname $(realpath $0))/authconfig.yaml"} -authconfig_invalid=${AUTHCONFIG_INVALID:-"$(dirname $(realpath $0))/authconfig-invalid.yaml"} +authconfig_version=${AUTHCONFIG_VERSION:-"v1beta2"} +authconfig=${AUTHCONFIG:-"$(dirname $(realpath $0))/${authconfig_version}/authconfig.yaml"} +authconfig_invalid=${AUTHCONFIG_INVALID:-"$(dirname $(realpath $0))/${authconfig_version}/authconfig-invalid.yaml"} verbose=${VERBOSE} timeout=${TIMEOUT:-"600"} diff --git a/tests/authconfig-invalid.yaml b/tests/v1beta1/authconfig-invalid.yaml similarity index 100% rename from tests/authconfig-invalid.yaml rename to tests/v1beta1/authconfig-invalid.yaml diff --git a/tests/authconfig.yaml b/tests/v1beta1/authconfig.yaml similarity index 100% rename from tests/authconfig.yaml rename to tests/v1beta1/authconfig.yaml diff --git a/tests/v1beta2/authconfig-invalid.yaml b/tests/v1beta2/authconfig-invalid.yaml new file mode 100644 index 00000000..56b101a8 --- /dev/null +++ b/tests/v1beta2/authconfig-invalid.yaml @@ -0,0 +1,30 @@ +apiVersion: authorino.kuadrant.io/v1beta2 +kind: AuthConfig +metadata: + name: e2e-test-invalid +spec: + hosts: + - talker-api-authorino.127.0.0.1.nip.io + + authentication: + multiple-identity-methods: + apiKey: + selector: + matchLabels: + app: talker-api + jwt: + issuerUrl: http://keycloak.authorino.svc.cluster.local:8080/auth/realms/kuadrant + + metadata: + multiple-metadata-methods: + http: + url: http://metadata-service + userInfo: + identitySource: keycloak + + authorization: + multiple-authorization-methods: + opa: + rego: allow = true + patternMatching: + patterns: [] diff --git a/tests/v1beta2/authconfig.yaml b/tests/v1beta2/authconfig.yaml new file mode 100644 index 00000000..ab12812d --- /dev/null +++ b/tests/v1beta2/authconfig.yaml @@ -0,0 +1,308 @@ +apiVersion: authorino.kuadrant.io/v1beta2 +kind: AuthConfig +metadata: + name: e2e-test +spec: + hosts: + - talker-api-authorino.127.0.0.1.nip.io + + patterns: + admin-path: + - selector: context.request.http.path + operator: matches + value: ^/admin(/.*)?$ + resource-path: + - selector: context.request.http.path + operator: matches + value: ^/greetings/\d+$ + + authentication: + k8s-auth: + kubernetesTokenReview: + audiences: + - https://kubernetes.default.svc.cluster.local + defaults: + kubernetes-rbac: + value: true + username: + selector: auth.identity.user.username + value: null + api-key: + apiKey: + selector: + matchLabels: + app: talker-api + credentials: + customHeader: + name: X-API-KEY + defaults: + kubernetes-rbac: + value: true + username: + selector: auth.identity.metadata.annotations.username + keycloak: + jwt: + issuerUrl: http://keycloak.authorino.svc.cluster.local:8080/auth/realms/kuadrant + ttl: 60 + defaults: + jwt-rbac: + value: true + roles: + selector: auth.identity.realm_access.roles + username: + selector: auth.identity.preferred_username + oauth2-introspection: + oauth2Introspection: + credentialsRef: + name: oauth2-token-introspection-credentials-keycloak + endpoint: http://keycloak.authorino.svc.cluster.local:8080/auth/realms/kuadrant/protocol/openid-connect/token/introspect + tokenTypeHint: requesting_party_token + credentials: + authorizationHeader: + prefix: Opaque + defaults: + jwt-rbac: + value: true + roles: + selector: auth.identity.realm_access.roles + username: + selector: auth.identity.preferred_username + cache: + key: + selector: context.request.http.headers.authorization + anonymous: + anonymous: {} + priority: 1 + when: + - selector: context.request.http.method + operator: eq + value: GET + - selector: context.request.http.path + operator: matches + value: ^/$ + defaults: + username: + value: global + + metadata: + geo-info: + http: + credentials: {} + headers: + Accept: + value: application/json + method: GET + url: http://ip-location.authorino.svc.cluster.local:3000/{context.request.http.headers.x-forwarded-for.@extract:{"sep":","}} + cache: + key: + selector: context.request.http.headers.x-forwarded-for.@extract:{"sep":","} + user-info: + userInfo: + identitySource: keycloak + cache: + key: + selector: context.request.http.headers.authorization + resource-info: + when: + - patternRef: resource-path + uma: + credentialsRef: + name: talker-api-uma-credentials + endpoint: http://keycloak.authorino.svc.cluster.local:8080/auth/realms/kuadrant + cache: + key: + selector: context.request.http.path + + authorization: + allowed-methods: + opa: + externalPolicy: + url: https://raw.githubusercontent.com/guicassolato/authorino-opa/main/allowed-methods.rego + ttl: 300 + geofence: + opa: + allValues: true + rego: | + country = object.get(object.get(input.auth.metadata, "geo-info", {}), "country_iso_code", null) + allow { + allowed_countries := ["ES", "FR", "IT"] + allowed_countries[_] == country + } + admin-kubernetes-rbac: + when: + - patternRef: admin-path + - selector: auth.identity.kubernetes-rbac + operator: eq + value: 'true' + kubernetesSubjectAccessReview: + user: + selector: auth.identity.username + admin-jwt-rbac: + when: + - patternRef: admin-path + - selector: auth.identity.jwt-rbac + operator: eq + value: 'true' + patternMatching: + patterns: + - selector: auth.identity.roles + operator: incl + value: admin + resource-owner: + when: + - patternRef: resource-path + opa: + rego: | + allow { + resource_attrs := object.get(input.auth.metadata, "resource-info", [])[0] + resource_owner := object.get(object.get(resource_attrs, "owner", {}), "id", "") + resource_owner == input.auth.identity.sub + } + timestamp: + opa: + rego: | + now = time.now_ns() / 1000000000 + allow = true + allValues: true + priority: 1 + + response: + unauthenticated: + message: + value: Authentication failed + unauthorized: + message: + value: Access denied + success: + headers: + x-auth-service: + plain: + value: Authorino + x-username: + plain: + selector: auth.identity.username + x-auth-data: + json: + properties: + username: + selector: auth.identity.username + geo: + selector: auth.metadata.geo-info + timestamp: + selector: auth.authorization.timestamp.now + wristband: + wristband: + issuer: https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/wristband + tokenDuration: 300 + customClaims: + username: + selector: auth.identity.username + uri: + selector: context.request.http.path + scope: + selector: context.request.http.method.@case:lower + signingKeyRefs: + - name: wristband-signing-key + algorithm: ES256 + when: + - selector: auth.identity.anonymous + operator: neq + value: 'true' + dynamicMetadata: + rate-limit-data: + json: + properties: + username: + selector: auth.identity.username + key: ext_auth_data +--- +apiVersion: v1 +kind: Secret +metadata: + name: oauth2-token-introspection-credentials-keycloak +stringData: + clientID: talker-api + clientSecret: 523b92b6-625d-4e1e-a313-77e7a8ae4e88 +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: talker-api-uma-credentials +stringData: + clientID: talker-api + clientSecret: 523b92b6-625d-4e1e-a313-77e7a8ae4e88 +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: wristband-signing-key +stringData: + key.pem: | + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIDHvuf81gVlWGo0hmXGTAnA/HVxGuH8vOc7/8jewcVvqoAoGCCqGSM49 + AwEHoUQDQgAETJf5NLVKplSYp95TOfhVPqvxvEibRyjrUZwwtpDuQZxJKDysoGwn + cnUvHIu23SgW+Ee9lxSmZGhO4eTdQeKxMA== + -----END EC PRIVATE KEY----- +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: bob-api-key + labels: + authorino.kuadrant.io/managed-by: authorino + app: talker-api + annotations: + username: bob +stringData: + api_key: ndyBzreUzF4zqDQsqSPMHkRhriEOtcRx +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: alice-api-key + labels: + authorino.kuadrant.io/managed-by: authorino + app: talker-api + annotations: + username: alice +stringData: + api_key: pR2zLorYFIYOE4LLiQAWMPIRei1YgRBy +type: Opaque +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: app-1-sa +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: app-2-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: talker-api-admin +rules: +- nonResourceURLs: ["/admin*"] + verbs: ["get", "post"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: talker-api-admins +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: talker-api-admin +subjects: +- kind: User + name: bob + namespace: bob +- kind: ServiceAccount + name: app-1-sa + namespace: authorino From 6c22f27dd12b965f0c6ebdefd4e4150ecbd9d5d0 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 6 Sep 2023 12:42:49 +0200 Subject: [PATCH 29/35] Remove unnecessary steps to install cert-manager when relying on the new Authorino Operator install script --- .github/workflows/integration-test.yaml | 6 ----- docs/contributing.md | 2 +- docs/getting-started.md | 16 +++-------- docs/user-guides/mtls-authentication.md | 35 ++----------------------- docs/user-guides/validating-webhook.md | 9 ++----- 5 files changed, 9 insertions(+), 59 deletions(-) diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 1cd933f1..b5ca3368 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -31,12 +31,6 @@ jobs: run: go install sigs.k8s.io/kind@v0.20.0 - name: Create kind cluster run: kind create cluster --name authorino-smoke-tests - - name: Install cert-manager - run: | - kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml - kubectl delete mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook - kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io/cert-manager-webhook - kubectl -n cert-manager wait --timeout=300s --for=condition=Available deployments --all - name: Install Authorino Operator run: | curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s diff --git a/docs/contributing.md b/docs/contributing.md index 248319f1..459d36e5 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -90,10 +90,10 @@ make test The following command will: - Start a local Kubernetes cluster (using Kind) +- Install [cert-manager](https://github.com/jetstack/cert-manager) in the cluster - Install the [Authorino Operator](https://github.com/kuadrant/authorino-operator) and Authorino CRDs - Build an image of Authorino based on the current branch - Push the freshly built image to the cluster's registry -- Install [cert-manager](https://github.com/jetstack/cert-manager) in the cluster - Generate TLS certificates for the Authorino service - Deploy an instance of Authorino - Deploy the example application [**Talker API**](https://github.com/kuadrant/authorino-examples#talker-api), a simple HTTP API that echoes back whatever it gets in the request diff --git a/docs/getting-started.md b/docs/getting-started.md index 552d9331..34dbb85b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -56,6 +56,8 @@ curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/util The above will install the latest build of the Authorino Operator and latest version of the manifests (CRDs and RBAC), which by default points as well to the latest build of Authorino, both based on the `main` branches of each component. To install a stable released version of the Operator and therefore also defaults to its latest compatible stable release of Authorino, replace `main` with another tag of a proper release of the Operator, e.g. 'v0.2.0'. +This step will also install [cert-manager](https://github.com/jetstack/cert-manager) in the cluster (required). + Alternatively, you can deploy the Authorino Operator using the Operator Lifecycle Manager bundles. For instructions, check out [Installing via OLM](https://github.com/kuadrant/authorino-operator#installing-via-olm). ### Step: Request an Authorino instance @@ -72,12 +74,7 @@ The instructions here are for centralized gateway or centralized authorization s kubectl create namespace authorino ``` - Deploy [cert-manager](https://github.com/jetstack/cert-manager) (skip if you already have certificates and certificate keys created and stored in Kubernetes `Secret`s in the namespace or cert-manager is installed and running in the cluster): - ```sh - kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml - ``` - - Create the TLS certificates (skip if you already have certificates and certificate keys created and stored in Kubernetes `Secret`s in the namespace): + Create the TLS certificates (requires [cert-manager](https://github.com/jetstack/cert-manager); skip if you already have certificates and certificate keys created and stored in Kubernetes `Secret`s in the namespace): ```sh curl -sSL https://raw.githubusercontent.com/Kuadrant/authorino/main/deploy/certs.yaml | sed "s/\$(AUTHORINO_INSTANCE)/authorino/g;s/\$(NAMESPACE)/authorino/g" | kubectl -n authorino apply -f - ``` @@ -138,12 +135,7 @@ The instructions here are for centralized gateway or centralized authorization s kubectl create namespace myapp ``` - Deploy [cert-manager](https://github.com/jetstack/cert-manager) (skip if you already have certificates and certificate keys created and stored in Kubernetes `Secret`s in the namespace or cert-manager is installed and running in the cluster): - ```sh - kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml - ``` - - Create the TLS certificates (skip if you already have certificates and certificate keys created and stored in Kubernetes `Secret`s in the namespace): + Create the TLS certificates (requires [cert-manager](https://github.com/jetstack/cert-manager); skip if you already have certificates and certificate keys created and stored in Kubernetes `Secret`s in the namespace): ```sh curl -sSL https://raw.githubusercontent.com/Kuadrant/authorino/main/deploy/certs.yaml | sed "s/\$(AUTHORINO_INSTANCE)/authorino/g;s/\$(NAMESPACE)/myapp/g" | kubectl -n myapp apply -f - ``` diff --git a/docs/user-guides/mtls-authentication.md b/docs/user-guides/mtls-authentication.md index 797ab342..6f73bda4 100644 --- a/docs/user-guides/mtls-authentication.md +++ b/docs/user-guides/mtls-authentication.md @@ -23,7 +23,6 @@ Verify client X.509 certificates against trusted root CAs stored in Kubernetes ` ## Requirements - Kubernetes server -- [cert-manager](https://github.com/jetstack/cert-manager) Create a containerized Kubernetes server locally using [Kind](https://kind.sigs.k8s.io): @@ -31,18 +30,14 @@ Create a containerized Kubernetes server locally using [Kind](https://kind.sigs. kind create cluster --name authorino-tutorial ``` -Install cert-manager in the cluster: - -```sh -kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml -``` - ## 1. Install the Authorino Operator ```sh curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` +This step will also install [cert-manager](https://github.com/jetstack/cert-manager) in the cluster (required). + ## 2. Deploy Authorino Create the TLS certificates for the Authorino service: @@ -388,32 +383,6 @@ curl -k --cert /tmp/aisha.crt --key /tmp/aisha.key https://talker-api-authorino. ## Cleanup -If you have started a Kubernetes cluster locally with Kind to try this user guide, delete it by running: - ```sh kind delete cluster --name authorino-tutorial ``` - -Otherwise, delete the resources created in each step: - -```sh -kubectl delete authconfig/talker-api-protection -kubectl delete authorino/authorino -kubectl delete ingress/service -kubectl delete configmap/service -kubectl delete configmap/deployment -kubectl delete configmap/envoy -kubectl delete -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/talker-api/talker-api-deploy.yaml -``` - -To uninstall the Authorino Operator and manifests (CRDs, RBAC, etc), run: - -```sh -kubectl delete -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml -``` - -To uninstall the cert-manager, run: - -```sh -kubectl delete -f kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml -``` diff --git a/docs/user-guides/validating-webhook.md b/docs/user-guides/validating-webhook.md index c82ee717..8aeeb312 100644 --- a/docs/user-guides/validating-webhook.md +++ b/docs/user-guides/validating-webhook.md @@ -43,7 +43,6 @@ For convenience, the same instance of Authorino used to enforce the AuthConfig a ## Requirements - Kubernetes server -- [cert-manager](https://github.com/jetstack/cert-manager) - Auth server / Identity Provider (IdP) that implements OpenID Connect authentication and OpenID Connect Discovery (e.g. [Keycloak](https://www.keycloak.org)) Create a containerized Kubernetes server locally using [Kind](https://kind.sigs.k8s.io): @@ -52,12 +51,6 @@ Create a containerized Kubernetes server locally using [Kind](https://kind.sigs. kind create cluster --name authorino-tutorial ``` -Install cert-manager: - -```sh -kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml -``` - Deploy a Keycloak server preloaded with all the realm settings required for this guide: ```sh @@ -71,6 +64,8 @@ kubectl -n keycloak apply -f https://raw.githubusercontent.com/kuadrant/authorin curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s ``` +This step will also install [cert-manager](https://github.com/jetstack/cert-manager) in the cluster (required). + ## 2. Deploy Authorino Create the namespace: From 2fa28d7e91550fa23dab8fb3ef44fb4dc94710d5 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 6 Sep 2023 17:14:31 +0200 Subject: [PATCH 30/35] docs: fixup: 'Edge Authentication Architecture (EAA)' user guide --- .../edge-authentication-architecture-festival-wristbands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guides/edge-authentication-architecture-festival-wristbands.md b/docs/user-guides/edge-authentication-architecture-festival-wristbands.md index 24e3d804..902f9054 100644 --- a/docs/user-guides/edge-authentication-architecture-festival-wristbands.md +++ b/docs/user-guides/edge-authentication-architecture-festival-wristbands.md @@ -218,7 +218,7 @@ spec: authentication: "edge-authenticated": jwt: - issuerEndpoint: http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/edge/edge-auth/wristband + issuerUrl: http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/edge/edge-auth/wristband EOF ``` From 166c0885787a0b5ad997b2b2885f8cef2e1e6c5c Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 6 Sep 2023 18:01:00 +0200 Subject: [PATCH 31/35] docs: fixup: 'Authenticated rate limiting (with Envoy Dynamic Metadata)' user guide --- .../authenticated-rate-limiting-envoy-dynamic-metadata.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md b/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md index e2160e1b..e708a235 100644 --- a/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md +++ b/docs/user-guides/authenticated-rate-limiting-envoy-dynamic-metadata.md @@ -117,8 +117,8 @@ spec: "rate-limit": json: properties: - - name: username - selector: auth.identity.metadata.annotations.auth-data\/username + "username": + selector: auth.identity.metadata.annotations.auth-data\/username key: ext_auth_data # how this bit of dynamic metadata from the ext authz service is named in the Envoy config EOF ``` From bf6e0688241df1b6e2fb75d270634684ce133db5 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 13 Sep 2023 16:03:51 +0200 Subject: [PATCH 32/35] fix: docs: typo in the oauth2 token introspection user guide --- docs/user-guides/oauth2-token-introspection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guides/oauth2-token-introspection.md b/docs/user-guides/oauth2-token-introspection.md index 7f11463e..35002b58 100644 --- a/docs/user-guides/oauth2-token-introspection.md +++ b/docs/user-guides/oauth2-token-introspection.md @@ -289,7 +289,7 @@ kubectl delete authorino/authorino kubectl delete -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/envoy/envoy-notls-deploy.yaml kubectl delete -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/talker-api/talker-api-deploy.yaml kubectl delete namespace keycloak -kubectl delete namespace a12-server +kubectl delete namespace a12n-server ``` To uninstall the Authorino Operator and manifests (CRDs, RBAC, etc), run: From 5059dca1703d505efd76d6155419c02114f7fce4 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 13 Sep 2023 16:34:00 +0200 Subject: [PATCH 33/35] fix: docs: use net.cidr_contains instead of regex.match in opa example to avoid shell command issues --- docs/user-guides/opa-authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guides/opa-authorization.md b/docs/user-guides/opa-authorization.md index 3b0f6144..d4e856e5 100644 --- a/docs/user-guides/opa-authorization.md +++ b/docs/user-guides/opa-authorization.md @@ -113,7 +113,7 @@ spec: opa: rego: | ips := split(input.context.request.http.headers["x-forwarded-for"], ",") - trusted_network { regex.match(`192\.168\.1\.\d+`, ips[0]) } + trusted_network { net.cidr_contains("192.168.1.1/24", ips[0]) } allow { trusted_network } allow { not trusted_network; input.context.request.http.method == "GET" } From 4343ef8d886b16b8b5579aa5cc2b47b0c11a9f07 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 13 Sep 2023 16:52:28 +0200 Subject: [PATCH 34/35] fix: docs: wrong kubectl command in the cleanup section of a user guide --- docs/user-guides/validating-webhook.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guides/validating-webhook.md b/docs/user-guides/validating-webhook.md index 8aeeb312..b43c7ea8 100644 --- a/docs/user-guides/validating-webhook.md +++ b/docs/user-guides/validating-webhook.md @@ -552,7 +552,7 @@ Otherwise, delete the resources created in each step: ```sh kubectl delete namespace myapp kubectl delete namespace authorino -kubectl delete namespace clusterrole/authorino-apikey +kubectl delete clusterrole authorino-apikey kubectl delete namespace keycloak ``` From 90ce6fe30eaeb65146dd7d4b3a417459b1981768 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 13 Sep 2023 17:25:57 +0200 Subject: [PATCH 35/35] docs: urls with interpolated json paths within authconfig resources wrapped within quotes to avoid auto-escaping when pasting in a few terminals --- docs/user-guides/caching.md | 2 +- docs/user-guides/deny-with-redirect-to-login.md | 4 ++-- docs/user-guides/envoy-jwt-authn-and-authorino.md | 2 +- docs/user-guides/external-metadata.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/user-guides/caching.md b/docs/user-guides/caching.md index 9db1779f..487bdb46 100644 --- a/docs/user-guides/caching.md +++ b/docs/user-guides/caching.md @@ -109,7 +109,7 @@ spec: metadata: "cached-metadata": http: - url: http://talker-api.default.svc.cluster.local:3000/metadata/{context.request.http.path} + url: "http://talker-api.default.svc.cluster.local:3000/metadata/{context.request.http.path}" cache: key: selector: context.request.http.path diff --git a/docs/user-guides/deny-with-redirect-to-login.md b/docs/user-guides/deny-with-redirect-to-login.md index 3c5d85b5..583488cb 100644 --- a/docs/user-guides/deny-with-redirect-to-login.md +++ b/docs/user-guides/deny-with-redirect-to-login.md @@ -114,7 +114,7 @@ spec: code: 302 headers: "Location": - selector: http://matrix-quotes-authorino.127.0.0.1.nip.io:8000/login.html?redirect_to={context.request.http.path} + selector: "http://matrix-quotes-authorino.127.0.0.1.nip.io:8000/login.html?redirect_to={context.request.http.path}" EOF ``` @@ -216,7 +216,7 @@ spec: code: 302 headers: "Location": - selector: http://keycloak:8080/auth/realms/kuadrant/protocol/openid-connect/auth?client_id=matrix-quotes&redirect_uri=http://matrix-quotes-authorino.127.0.0.1.nip.io:8000/auth?redirect_to={context.request.http.path}&scope=openid&response_type=code + selector: "http://keycloak:8080/auth/realms/kuadrant/protocol/openid-connect/auth?client_id=matrix-quotes&redirect_uri=http://matrix-quotes-authorino.127.0.0.1.nip.io:8000/auth?redirect_to={context.request.http.path}&scope=openid&response_type=code" EOF ``` diff --git a/docs/user-guides/envoy-jwt-authn-and-authorino.md b/docs/user-guides/envoy-jwt-authn-and-authorino.md index 3e84eabf..146c341a 100644 --- a/docs/user-guides/envoy-jwt-authn-and-authorino.md +++ b/docs/user-guides/envoy-jwt-authn-and-authorino.md @@ -310,7 +310,7 @@ spec: metadata: "geoinfo": http: - url: http://ip-location.default.svc.cluster.local:3000/{context.request.http.headers.x-forwarded-for.@extract:{"sep":","}} + url: 'http://ip-location.default.svc.cluster.local:3000/{context.request.http.headers.x-forwarded-for.@extract:{"sep":","}}' headers: "Accept": value: application/json diff --git a/docs/user-guides/external-metadata.md b/docs/user-guides/external-metadata.md index 8a8a0ba9..6b0a7b42 100644 --- a/docs/user-guides/external-metadata.md +++ b/docs/user-guides/external-metadata.md @@ -110,7 +110,7 @@ spec: metadata: "geo": http: - url: http://ip-api.com/json/{context.request.http.headers.x-forwarded-for.@extract:{"sep":","}}?fields=countryCode + url: 'http://ip-api.com/json/{context.request.http.headers.x-forwarded-for.@extract:{"sep":","}}?fields=countryCode' headers: "Accept": value: application/json