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://github.com/raw/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://github.com/raw/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 {