diff --git a/apis/gateway/v1beta1/apirule_types.go b/apis/gateway/v1beta1/apirule_types.go index 607e483d9..3de642bab 100644 --- a/apis/gateway/v1beta1/apirule_types.go +++ b/apis/gateway/v1beta1/apirule_types.go @@ -16,6 +16,7 @@ limitations under the License. package v1beta1 import ( + "github.com/kyma-project/api-gateway/apis/gateway/versions" "istio.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -73,6 +74,10 @@ type APIRuleStatus struct { AuthorizationPolicyStatus *APIRuleResourceStatus `json:"authorizationPolicyStatus,omitempty"` } +func (s *APIRuleStatus) ApiRuleStatusVersion() versions.Version { + return versions.V1beta1 +} + // APIRule is the Schema for ApiRule APIs. // +kubebuilder:storageversion // +kubebuilder:deprecatedversion:warning=APIRule version v1beta1 is deprecated in favor of v2alpha1. Please migrate APIRule to v2alpha1 as soon as possible. diff --git a/apis/gateway/v2alpha1/apirule_types.go b/apis/gateway/v2alpha1/apirule_types.go index 7028fc461..88a0b0c19 100644 --- a/apis/gateway/v2alpha1/apirule_types.go +++ b/apis/gateway/v2alpha1/apirule_types.go @@ -16,6 +16,7 @@ limitations under the License. package v2alpha1 import ( + "github.com/kyma-project/api-gateway/apis/gateway/versions" "istio.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -65,12 +66,17 @@ type APIRuleStatus struct { // State signifies current state of APIRule. // Value can be one of ("Ready", "Processing", "Error", "Deleting", "Warning"). // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=Processing;Deleting;Ready;Error;Warning State State `json:"state"` // Description of APIRule status Description string `json:"description,omitempty"` } +func (s *APIRuleStatus) ApiRuleStatusVersion() versions.Version { + return versions.V2alpha1 +} + // APIRule is the Schema for ApiRule APIs. // +kubebuilder:object:root=true // +kubebuilder:subresource:status diff --git a/apis/gateway/versions/versions.go b/apis/gateway/versions/versions.go new file mode 100644 index 000000000..63ea9ac9b --- /dev/null +++ b/apis/gateway/versions/versions.go @@ -0,0 +1,19 @@ +package versions + +const ( + V1beta1 = iota + V2alpha1 +) + +type Version int + +func (v Version) String() string { + switch v { + case V1beta1: + return "v1beta1" + case V2alpha1: + return "v2alpha1" + default: + return "unknown" + } +} diff --git a/config/crd/bases/gateway.kyma-project.io_apirules.yaml b/config/crd/bases/gateway.kyma-project.io_apirules.yaml index 94d79f386..c094b8e9d 100644 --- a/config/crd/bases/gateway.kyma-project.io_apirules.yaml +++ b/config/crd/bases/gateway.kyma-project.io_apirules.yaml @@ -546,9 +546,6 @@ spec: format: date-time type: string state: - description: |- - State signifies current state of APIRule. - Value can be one of ("Ready", "Processing", "Error", "Deleting", "Warning"). enum: - Processing - Deleting diff --git a/controllers/gateway/apirule_controller.go b/controllers/gateway/apirule_controller.go index 1d538967b..b31f4157d 100644 --- a/controllers/gateway/apirule_controller.go +++ b/controllers/gateway/apirule_controller.go @@ -20,6 +20,9 @@ import ( "context" "encoding/json" "fmt" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" + v2alpha1Processing "github.com/kyma-project/api-gateway/internal/processing/processors/v2alpha1" + "github.com/kyma-project/api-gateway/internal/processing/status" "time" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" @@ -33,8 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/builder" "github.com/kyma-project/api-gateway/internal/helpers" - "github.com/kyma-project/api-gateway/internal/processing/istio" - "github.com/kyma-project/api-gateway/internal/processing/ory" + "github.com/kyma-project/api-gateway/internal/processing/processors/ory" "github.com/kyma-project/api-gateway/internal/validation" "github.com/go-logr/logr" @@ -89,6 +91,85 @@ func (p isApiGatewayConfigMapPredicate) Generic(e event.GenericEvent) bool { return okCM && configMap.GetNamespace() == CONFIGMAP_NS && configMap.GetName() == CONFIGMAP_NAME } +func (r *APIRuleReconciler) reconcileConfigMap(ctx context.Context, isCMReconcile bool) (finishReconciliation bool) { + r.Log.Info("Starting ConfigMap reconciliation") + err := r.Config.ReadFromConfigMap(ctx, r.Client) + if err != nil { + if apierrs.IsNotFound(err) { + r.Log.Info(fmt.Sprintf(`ConfigMap %s in namespace %s was not found {"controller": "Api"}, will use default config`, helpers.CM_NAME, helpers.CM_NS)) + r.Config.ResetToDefault() + } else { + r.Log.Error(err, fmt.Sprintf(`could not read ConfigMap %s in namespace %s {"controller": "Api"}`, helpers.CM_NAME, helpers.CM_NS)) + r.Config.Reset() + } + } + if isCMReconcile { + configValidationFailures := validation.ValidateConfig(r.Config) + r.Log.Info("ConfigMap changed", "config", r.Config) + if len(configValidationFailures) > 0 { + failuresJson, _ := json.Marshal(configValidationFailures) + r.Log.Error(err, fmt.Sprintf(`Config validation failure {"controller": "Api", "failures": %s}`, string(failuresJson))) + } + r.Log.Info("ConfigMap reconciliation finished") + return true + } + return false +} + +func (r *APIRuleReconciler) handleAPIRuleGetError(ctx context.Context, name types.NamespacedName, apiRule *gatewayv1beta1.APIRule, err error, cmd processing.ReconciliationCommand) (ctrl.Result, error) { + if apierrs.IsNotFound(err) { + //There is no APIRule. Nothing to process, dependent objects will be garbage-collected. + r.Log.Info(fmt.Sprintf("Finishing reconciliation as ApiRule '%s' does not exist.", name)) + return doneReconcileNoRequeue() + } + + r.Log.Error(err, "Error getting ApiRule") + + statusBase := cmd.GetStatusBase(string(gatewayv1beta1.StatusSkipped)) + errorMap := map[status.ResourceSelector][]error{status.OnApiRule: {err}} + return r.updateStatusOrRetry(ctx, apiRule, statusBase.GetStatusForErrorMap(errorMap)) +} + +func (r *APIRuleReconciler) reconcileAPIRuleDeletion(ctx context.Context, apiRule *gatewayv1beta1.APIRule) (ctrl.Result, error) { + if controllerutil.ContainsFinalizer(apiRule, API_GATEWAY_FINALIZER) { + // finalizer is present on APIRule, so all subresources need to be deleted + if err := processing.DeleteAPIRuleSubresources(r.Client, ctx, *apiRule); err != nil { + r.Log.Error(err, "Error happened during deletion of APIRule subresources") + // if removing subresources ends in error, return with retry + // so that it can be retried + return doneReconcileErrorRequeue(r.OnErrorReconcilePeriod) + } + + // remove finalizer so the reconcilation can proceed + controllerutil.RemoveFinalizer(apiRule, API_GATEWAY_FINALIZER) + // workaround for when APIRule was deleted using v2alpha1 version and it got trimmed spec + if apiRule.Spec.Gateway == nil { + apiRule.Spec.Gateway = ptr.To("n/a") + } + if apiRule.Spec.Host == nil { + apiRule.Spec.Host = ptr.To("host") + } + if apiRule.Spec.Rules == nil { + apiRule.Spec.Rules = []gatewayv1beta1.Rule{{ + Methods: []gatewayv1beta1.HttpMethod{"GET"}, + Path: "/*", + AccessStrategies: []*gatewayv1beta1.Authenticator{ + { + Handler: &gatewayv1beta1.Handler{ + Name: "noop", + }, + }, + }, + }} + } + if err := r.Update(ctx, apiRule); err != nil { + r.Log.Error(err, "Error happened during finalizer removal") + return doneReconcileErrorRequeue(r.OnErrorReconcilePeriod) + } + } + return doneReconcileNoRequeue() +} + //+kubebuilder:rbac:groups=gateway.kyma-project.io,resources=apirules,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=gateway.kyma-project.io,resources=apirules/status,verbs=get;update;patch //+kubebuilder:rbac:groups=gateway.kyma-project.io,resources=apirules/finalizers,verbs=update @@ -107,43 +188,17 @@ func (r *APIRuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct ctx = logr.NewContext(ctx, r.Log) defaultDomainName, err := default_domain.GetDefaultDomainFromKymaGateway(ctx, r.Client) - if err != nil { - if apierrs.IsNotFound(err) { - r.Log.Error(err, "Default domain wasn't found. APIRules will require full host") - } else { - r.Log.Error(err, "Error getting default domain") - return doneReconcileErrorRequeue(ERROR_RECONCILIATION_PERIOD) - } - } - - validator := validation.APIRuleValidator{ - DefaultDomainName: defaultDomainName, + if err != nil && default_domain.HandleDefaultDomainError(r.Log, err) { + return doneReconcileErrorRequeue(ERROR_RECONCILIATION_PERIOD) } isCMReconcile := req.NamespacedName.String() == types.NamespacedName{Namespace: helpers.CM_NS, Name: helpers.CM_NAME}.String() - if isCMReconcile || r.Config.JWTHandler == "" { - r.Log.Info("Starting ConfigMap reconciliation") - err := r.Config.ReadFromConfigMap(ctx, r.Client) - if err != nil { - if apierrs.IsNotFound(err) { - r.Log.Info(fmt.Sprintf(`ConfigMap %s in namespace %s was not found {"controller": "Api"}, will use default config`, helpers.CM_NAME, helpers.CM_NS)) - r.Config.ResetToDefault() - } else { - r.Log.Error(err, fmt.Sprintf(`could not read ConfigMap %s in namespace %s {"controller": "Api"}`, helpers.CM_NAME, helpers.CM_NS)) - r.Config.Reset() - } - } - if isCMReconcile { - configValidationFailures := validator.ValidateConfig(r.Config) - r.Log.Info("ConfigMap changed", "config", r.Config) - if len(configValidationFailures) > 0 { - failuresJson, _ := json.Marshal(configValidationFailures) - r.Log.Error(err, fmt.Sprintf(`Config validation failure {"controller": "Api", "failures": %s}`, string(failuresJson))) - } - r.Log.Info("ConfigMap reconciliation finished") - return doneReconcileNoRequeue() - } + + finishReconcile := r.reconcileConfigMap(ctx, isCMReconcile) + if finishReconcile { + return doneReconcileNoRequeue() } + r.Log.Info("Starting ApiRule reconciliation", "jwtHandler", r.Config.JWTHandler) isApiRuleInv2alpha1Version := false @@ -156,23 +211,13 @@ func (r *APIRuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct cmd := r.getReconciliation(defaultDomainName, isApiRuleInv2alpha1Version) if apiRuleErr != nil { - if apierrs.IsNotFound(apiRuleErr) { - //There is no APIRule. Nothing to process, dependent objects will be garbage-collected. - r.Log.Info(fmt.Sprintf("Finishing reconciliation as ApiRule '%s' does not exist.", req.NamespacedName)) - return doneReconcileNoRequeue() - } - - r.Log.Error(apiRuleErr, "Error getting ApiRule") - - statusBase := cmd.GetStatusBase(gatewayv1beta1.StatusSkipped) - errorMap := map[processing.ResourceSelector][]error{processing.OnApiRule: {apiRuleErr}} - status := processing.GetStatusForErrorMap(errorMap, statusBase) - return r.updateStatusOrRetry(ctx, apiRule, status) + return r.handleAPIRuleGetError(ctx, req.NamespacedName, apiRule, apiRuleErr, cmd) } r.Log.Info("Reconciling ApiRule", "name", apiRule.Name, "namespace", apiRule.Namespace, "resource version", apiRule.ResourceVersion) - if apiRule.DeletionTimestamp.IsZero() { + shouldDeleteAPIRule := !apiRule.DeletionTimestamp.IsZero() + if !shouldDeleteAPIRule { if name, err := dependencies.APIRule().AreAvailable(ctx, r.Client); err != nil { status, err := handleDependenciesError(name, err).ToAPIRuleStatus() if err != nil { @@ -188,56 +233,20 @@ func (r *APIRuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } } } else { - if controllerutil.ContainsFinalizer(apiRule, API_GATEWAY_FINALIZER) { - // finalizer is present on APIRule, so all subresources need to be deleted - if err := processing.DeleteAPIRuleSubresources(r.Client, ctx, *apiRule); err != nil { - r.Log.Error(err, "Error happened during deletion of APIRule subresources") - // if removing subresources ends in error, return with retry - // so that it can be retried - return doneReconcileErrorRequeue(r.OnErrorReconcilePeriod) - } - - // remove finalizer so the reconcilation can proceed - controllerutil.RemoveFinalizer(apiRule, API_GATEWAY_FINALIZER) - // workaround for when APIRule was deleted using v2alpha1 version and it got trimmed spec - if apiRule.Spec.Gateway == nil { - apiRule.Spec.Gateway = ptr.To("n/a") - } - if apiRule.Spec.Host == nil { - apiRule.Spec.Host = ptr.To("host") - } - if apiRule.Spec.Rules == nil { - apiRule.Spec.Rules = []gatewayv1beta1.Rule{{ - Methods: []gatewayv1beta1.HttpMethod{"GET"}, - Path: "/*", - AccessStrategies: []*gatewayv1beta1.Authenticator{ - { - Handler: &gatewayv1beta1.Handler{ - Name: "noop", - }, - }, - }, - }} - } - if err := r.Update(ctx, apiRule); err != nil { - r.Log.Error(err, "Error happened during finalizer removal") - return doneReconcileErrorRequeue(r.OnErrorReconcilePeriod) - } - } - return doneReconcileNoRequeue() + return r.reconcileAPIRuleDeletion(ctx, apiRule) } r.Log.Info("Validating ApiRule config") - configValidationFailures := validator.ValidateConfig(r.Config) + configValidationFailures := validation.ValidateConfig(r.Config) if len(configValidationFailures) > 0 { failuresJson, _ := json.Marshal(configValidationFailures) r.Log.Error(err, fmt.Sprintf(`Config validation failure {"controller": "Api", "request": "%s/%s", "failures": %s}`, apiRule.Namespace, apiRule.Name, string(failuresJson))) - statusBase := cmd.GetStatusBase(gatewayv1beta1.StatusSkipped) - return r.updateStatusOrRetry(ctx, apiRule, processing.GenerateStatusFromFailures(configValidationFailures, statusBase)) + statusBase := cmd.GetStatusBase(string(gatewayv1beta1.StatusSkipped)) + return r.updateStatusOrRetry(ctx, apiRule, statusBase.GenerateStatusFromFailures(configValidationFailures)) } - status := processing.Reconcile(ctx, r.Client, &r.Log, cmd, apiRule) - return r.updateStatusOrRetry(ctx, apiRule, status) + s := processing.Reconcile(ctx, r.Client, &r.Log, cmd, apiRule) + return r.updateStatusOrRetry(ctx, apiRule, s) } func handleDependenciesError(name string, err error) controllers.Status { @@ -251,11 +260,14 @@ func handleDependenciesError(name string, err error) controllers.Status { func (r *APIRuleReconciler) getReconciliation(defaultDomain string, apiRulev2alpha1 bool) processing.ReconciliationCommand { config := r.ReconciliationConfig config.DefaultDomainName = defaultDomain - if r.Config.JWTHandler == helpers.JWT_HANDLER_ISTIO || apiRulev2alpha1 { + switch { + case apiRulev2alpha1: + return v2alpha1Processing.NewReconciliation(config, &r.Log) + case r.Config.JWTHandler == helpers.JWT_HANDLER_ISTIO: return istio.NewIstioReconciliation(config, &r.Log) + default: + return ory.NewOryReconciliation(config, &r.Log) } - return ory.NewOryReconciliation(config, &r.Log) - } // SetupWithManager sets up the controller with the Manager. @@ -271,7 +283,7 @@ func (r *APIRuleReconciler) SetupWithManager(mgr ctrl.Manager, c controllers.Rat } // Updates api status. If there was an error during update, returns the error so that entire reconcile loop is retried. If there is no error, returns a "reconcile success" value. -func (r *APIRuleReconciler) updateStatusOrRetry(ctx context.Context, api *gatewayv1beta1.APIRule, status processing.ReconciliationStatus) (ctrl.Result, error) { +func (r *APIRuleReconciler) updateStatusOrRetry(ctx context.Context, api *gatewayv1beta1.APIRule, status status.ReconciliationStatusVisitor) (ctrl.Result, error) { _, updateStatusErr := r.updateStatus(ctx, api, status) if updateStatusErr != nil { r.Log.Error(updateStatusErr, "Error updating ApiRule status, retrying") @@ -313,7 +325,7 @@ func retryReconcile(err error) (ctrl.Result, error) { return reconcile.Result{Requeue: true}, err } -func (r *APIRuleReconciler) updateStatus(ctx context.Context, api *gatewayv1beta1.APIRule, status processing.ReconciliationStatus) (*gatewayv1beta1.APIRule, error) { +func (r *APIRuleReconciler) updateStatus(ctx context.Context, api *gatewayv1beta1.APIRule, status status.ReconciliationStatusVisitor) (*gatewayv1beta1.APIRule, error) { api, err := r.getLatestApiRule(ctx, api) if err != nil { return nil, err @@ -321,11 +333,11 @@ func (r *APIRuleReconciler) updateStatus(ctx context.Context, api *gatewayv1beta api.Status.ObservedGeneration = api.Generation api.Status.LastProcessedTime = &v1.Time{Time: time.Now()} - api.Status.APIRuleStatus = status.ApiRuleStatus - api.Status.VirtualServiceStatus = status.VirtualServiceStatus - api.Status.AccessRuleStatus = status.AccessRuleStatus - api.Status.RequestAuthenticationStatus = status.RequestAuthenticationStatus - api.Status.AuthorizationPolicyStatus = status.AuthorizationPolicyStatus + + err = status.VisitStatus(&api.Status) + if err != nil { + return nil, err + } r.Log.Info("Updating ApiRule status", "status", api.Status) err = retry.RetryOnConflict(retry.DefaultRetry, func() error { diff --git a/controllers/status.go b/controllers/status.go index 94459f872..d918b9ab8 100644 --- a/controllers/status.go +++ b/controllers/status.go @@ -5,7 +5,7 @@ import ( "fmt" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" operatorv1alpha1 "github.com/kyma-project/api-gateway/apis/operator/v1alpha1" - "github.com/kyma-project/api-gateway/internal/processing" + v1beta1Status "github.com/kyma-project/api-gateway/internal/processing/status/v1beta1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/util/retry" @@ -25,7 +25,7 @@ const ( type Status interface { NestedError() error ToAPIGatewayStatus() (operatorv1alpha1.APIGatewayStatus, error) - ToAPIRuleStatus() (processing.ReconciliationStatus, error) + ToAPIRuleStatus() (v1beta1Status.ReconciliationV1beta1Status, error) IsReady() bool IsWarning() bool IsError() bool @@ -118,31 +118,31 @@ func (s status) ToAPIGatewayStatus() (operatorv1alpha1.APIGatewayStatus, error) } } -func (s status) ToAPIRuleStatus() (processing.ReconciliationStatus, error) { +func (s status) ToAPIRuleStatus() (v1beta1Status.ReconciliationV1beta1Status, error) { switch s.state { case Ready: - return processing.ReconciliationStatus{ + return v1beta1Status.ReconciliationV1beta1Status{ ApiRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ Code: gatewayv1beta1.StatusOK, Description: s.description, }, }, nil case Error: - return processing.ReconciliationStatus{ + return v1beta1Status.ReconciliationV1beta1Status{ ApiRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ Code: gatewayv1beta1.StatusError, Description: s.description, }, }, nil case Warning: - return processing.ReconciliationStatus{ + return v1beta1Status.ReconciliationV1beta1Status{ ApiRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ Code: gatewayv1beta1.StatusWarning, Description: s.description, }, }, nil default: - return processing.ReconciliationStatus{}, fmt.Errorf("unsupported status: %v", s.state) + return v1beta1Status.ReconciliationV1beta1Status{}, fmt.Errorf("unsupported status: %v", s.state) } } diff --git a/internal/processing/cleaner_test.go b/internal/processing/cleaner_test.go index b097d63a9..bf56cc70d 100644 --- a/internal/processing/cleaner_test.go +++ b/internal/processing/cleaner_test.go @@ -15,7 +15,7 @@ import ( . "github.com/onsi/gomega" "github.com/kyma-project/api-gateway/internal/processing" - testUtils "github.com/kyma-project/api-gateway/internal/processing/internal/test" + testUtils "github.com/kyma-project/api-gateway/internal/processing/processing_test" ) var _ = Describe("APIRule subresources deletion", func() { diff --git a/internal/processing/default_domain/default_domain.go b/internal/processing/default_domain/default_domain.go index f16a66b57..f80dfa5ee 100644 --- a/internal/processing/default_domain/default_domain.go +++ b/internal/processing/default_domain/default_domain.go @@ -3,9 +3,11 @@ package default_domain import ( "context" "fmt" + "github.com/go-logr/logr" "github.com/thoas/go-funk" apiv1beta1 "istio.io/api/networking/v1beta1" networkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" + apierrs "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "regexp" "sigs.k8s.io/controller-runtime/pkg/client" @@ -38,6 +40,16 @@ func GetHostLocalDomain(host string, namespace string) string { return fmt.Sprintf("%s.%s.svc.cluster.local", host, namespace) } +func HandleDefaultDomainError(log logr.Logger, err error) (finishReconciliation bool) { + if apierrs.IsNotFound(err) { + log.Error(err, "Default domain wasn't found. APIRules will require full host") + return false + } else { + log.Error(err, "Error getting default domain") + return true + } +} + func GetDefaultDomainFromKymaGateway(ctx context.Context, k8sClient client.Client) (string, error) { var gateway networkingv1beta1.Gateway err := k8sClient.Get(ctx, types.NamespacedName{Namespace: gatewayNamespace, Name: gatewayName}, &gateway) diff --git a/internal/processing/istio/status.go b/internal/processing/istio/status.go deleted file mode 100644 index 740fc1a87..000000000 --- a/internal/processing/istio/status.go +++ /dev/null @@ -1,27 +0,0 @@ -package istio - -import ( - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - "github.com/kyma-project/api-gateway/internal/processing" -) - -func (r Reconciliation) GetStatusBase(statusCode gatewayv1beta1.StatusCode) processing.ReconciliationStatus { - return StatusBase(statusCode) -} - -func StatusBase(statusCode gatewayv1beta1.StatusCode) processing.ReconciliationStatus { - return processing.ReconciliationStatus{ - ApiRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ - Code: statusCode, - }, - VirtualServiceStatus: &gatewayv1beta1.APIRuleResourceStatus{ - Code: statusCode, - }, - AuthorizationPolicyStatus: &gatewayv1beta1.APIRuleResourceStatus{ - Code: statusCode, - }, - RequestAuthenticationStatus: &gatewayv1beta1.APIRuleResourceStatus{ - Code: statusCode, - }, - } -} diff --git a/internal/processing/ory/status.go b/internal/processing/ory/status.go deleted file mode 100644 index 329071cce..000000000 --- a/internal/processing/ory/status.go +++ /dev/null @@ -1,24 +0,0 @@ -package ory - -import ( - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - "github.com/kyma-project/api-gateway/internal/processing" -) - -func (r Reconciliation) GetStatusBase(statusCode gatewayv1beta1.StatusCode) processing.ReconciliationStatus { - return (StatusBase(statusCode)) -} - -func StatusBase(statusCode gatewayv1beta1.StatusCode) processing.ReconciliationStatus { - return processing.ReconciliationStatus{ - ApiRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ - Code: statusCode, - }, - VirtualServiceStatus: &gatewayv1beta1.APIRuleResourceStatus{ - Code: statusCode, - }, - AccessRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ - Code: statusCode, - }, - } -} diff --git a/internal/processing/internal/test/test_utils.go b/internal/processing/processing_test/test_utils.go similarity index 100% rename from internal/processing/internal/test/test_utils.go rename to internal/processing/processing_test/test_utils.go diff --git a/internal/processing/processors/access_rule_processor_test.go b/internal/processing/processors/access_rule_processor_test.go index cf248d166..d078a5b7c 100644 --- a/internal/processing/processors/access_rule_processor_test.go +++ b/internal/processing/processors/access_rule_processor_test.go @@ -7,7 +7,7 @@ import ( "github.com/kyma-project/api-gateway/internal/builders" "github.com/kyma-project/api-gateway/internal/processing" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" "github.com/kyma-project/api-gateway/internal/processing/processors" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/internal/processing/istio/access_rule_processor.go b/internal/processing/processors/istio/access_rule_processor.go similarity index 100% rename from internal/processing/istio/access_rule_processor.go rename to internal/processing/processors/istio/access_rule_processor.go diff --git a/internal/processing/istio/access_rule_processor_test.go b/internal/processing/processors/istio/access_rule_processor_test.go similarity index 97% rename from internal/processing/istio/access_rule_processor_test.go rename to internal/processing/processors/istio/access_rule_processor_test.go index e0fe404fb..3c4a8a4eb 100644 --- a/internal/processing/istio/access_rule_processor_test.go +++ b/internal/processing/processors/istio/access_rule_processor_test.go @@ -7,9 +7,9 @@ import ( "net/http" "github.com/kyma-project/api-gateway/internal/processing" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/istio" - "github.com/kyma-project/api-gateway/internal/processing/ory" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" + "github.com/kyma-project/api-gateway/internal/processing/processors/ory" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rulev1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" diff --git a/internal/processing/istio/audience_test.go b/internal/processing/processors/istio/audience_test.go similarity index 96% rename from internal/processing/istio/audience_test.go rename to internal/processing/processors/istio/audience_test.go index 5ced08ed1..1fe73e554 100644 --- a/internal/processing/istio/audience_test.go +++ b/internal/processing/processors/istio/audience_test.go @@ -6,8 +6,8 @@ import ( gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" "github.com/kyma-project/api-gateway/internal/builders" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/istio" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" securityv1beta1 "istio.io/client-go/pkg/apis/security/v1beta1" diff --git a/internal/processing/istio/authorization_policy_processor.go b/internal/processing/processors/istio/authorization_policy_processor.go similarity index 100% rename from internal/processing/istio/authorization_policy_processor.go rename to internal/processing/processors/istio/authorization_policy_processor.go diff --git a/internal/processing/istio/authorization_policy_processor_test.go b/internal/processing/processors/istio/authorization_policy_processor_test.go similarity index 99% rename from internal/processing/istio/authorization_policy_processor_test.go rename to internal/processing/processors/istio/authorization_policy_processor_test.go index 77e74d7f0..66a31658b 100644 --- a/internal/processing/istio/authorization_policy_processor_test.go +++ b/internal/processing/processors/istio/authorization_policy_processor_test.go @@ -9,8 +9,8 @@ import ( "github.com/kyma-project/api-gateway/internal/processing/hashbasedstate" "github.com/kyma-project/api-gateway/internal/processing" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/istio" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" diff --git a/internal/processing/istio/istio_suite_test.go b/internal/processing/processors/istio/istio_suite_test.go similarity index 100% rename from internal/processing/istio/istio_suite_test.go rename to internal/processing/processors/istio/istio_suite_test.go diff --git a/internal/processing/istio/reconciliation.go b/internal/processing/processors/istio/reconciliation.go similarity index 75% rename from internal/processing/istio/reconciliation.go rename to internal/processing/processors/istio/reconciliation.go index 63d095f2c..4be981356 100644 --- a/internal/processing/istio/reconciliation.go +++ b/internal/processing/processors/istio/reconciliation.go @@ -2,13 +2,14 @@ package istio import ( "context" - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - "github.com/go-logr/logr" + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" "github.com/kyma-project/api-gateway/internal/processing" "github.com/kyma-project/api-gateway/internal/validation" networkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + + istioValidation "github.com/kyma-project/api-gateway/internal/validation/v1beta1/istio" ) type Reconciliation struct { @@ -34,15 +35,9 @@ func (r Reconciliation) Validate(ctx context.Context, client client.Client, apiR return make([]validation.Failure, 0), err } - validator := validation.APIRuleValidator{ - HandlerValidator: &handlerValidator{}, - AccessStrategiesValidator: &asValidator{}, - MutatorsValidator: &mutatorsValidator{}, - InjectionValidator: &injectionValidator{ctx: ctx, client: client}, - RulesValidator: &rulesValidator{}, - DefaultDomainName: r.config.DefaultDomainName, - } - return validator.Validate(ctx, client, apiRule, vsList), nil + validator := istioValidation.NewAPIRuleValidator(ctx, client, apiRule, r.config.DefaultDomainName) + + return validator.Validate(ctx, client, vsList), nil } func (r Reconciliation) GetProcessors() []processing.ReconciliationProcessor { diff --git a/internal/processing/istio/reconciliation_test.go b/internal/processing/processors/istio/reconciliation_test.go similarity index 98% rename from internal/processing/istio/reconciliation_test.go rename to internal/processing/processors/istio/reconciliation_test.go index 0813363ff..564f0057a 100644 --- a/internal/processing/istio/reconciliation_test.go +++ b/internal/processing/processors/istio/reconciliation_test.go @@ -5,8 +5,8 @@ import ( "fmt" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/istio" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rulev1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" diff --git a/internal/processing/istio/request_authentication_processor.go b/internal/processing/processors/istio/request_authentication_processor.go similarity index 100% rename from internal/processing/istio/request_authentication_processor.go rename to internal/processing/processors/istio/request_authentication_processor.go diff --git a/internal/processing/istio/request_authentication_processor_test.go b/internal/processing/processors/istio/request_authentication_processor_test.go similarity index 99% rename from internal/processing/istio/request_authentication_processor_test.go rename to internal/processing/processors/istio/request_authentication_processor_test.go index db151db72..d54891c37 100644 --- a/internal/processing/istio/request_authentication_processor_test.go +++ b/internal/processing/processors/istio/request_authentication_processor_test.go @@ -10,8 +10,8 @@ import ( typev1beta1 "istio.io/api/type/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/istio" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" diff --git a/internal/processing/processors/istio/status.go b/internal/processing/processors/istio/status.go new file mode 100644 index 000000000..3136492e5 --- /dev/null +++ b/internal/processing/processors/istio/status.go @@ -0,0 +1,28 @@ +package istio + +import ( + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/processing/status" + v1beta1Status "github.com/kyma-project/api-gateway/internal/processing/status/v1beta1" +) + +func (r Reconciliation) GetStatusBase(statusCode string) status.ReconciliationStatusVisitor { + return StatusBase(statusCode) +} + +func StatusBase(statusCode string) status.ReconciliationStatusVisitor { + return v1beta1Status.ReconciliationV1beta1Status{ + ApiRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusCode(statusCode), + }, + VirtualServiceStatus: &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusCode(statusCode), + }, + AuthorizationPolicyStatus: &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusCode(statusCode), + }, + RequestAuthenticationStatus: &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusCode(statusCode), + }, + } +} diff --git a/internal/processing/istio/status_test.go b/internal/processing/processors/istio/status_test.go similarity index 77% rename from internal/processing/istio/status_test.go rename to internal/processing/processors/istio/status_test.go index da5dd0971..71dcc6a67 100644 --- a/internal/processing/istio/status_test.go +++ b/internal/processing/processors/istio/status_test.go @@ -2,6 +2,7 @@ package istio import ( gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + v1beta1Status "github.com/kyma-project/api-gateway/internal/processing/status/v1beta1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -9,7 +10,7 @@ import ( var _ = Describe("IstioStatusBase", func() { It("should create status base with AccessRule set to nil", func() { // when - status := StatusBase(gatewayv1beta1.StatusSkipped) + status := StatusBase(string(gatewayv1beta1.StatusSkipped)).(v1beta1Status.ReconciliationV1beta1Status) Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusSkipped)) Expect(status.VirtualServiceStatus.Code).To(Equal(gatewayv1beta1.StatusSkipped)) diff --git a/internal/processing/istio/virtual_service_processor.go b/internal/processing/processors/istio/virtual_service_processor.go similarity index 100% rename from internal/processing/istio/virtual_service_processor.go rename to internal/processing/processors/istio/virtual_service_processor.go diff --git a/internal/processing/istio/virtual_service_processor_test.go b/internal/processing/processors/istio/virtual_service_processor_test.go similarity index 99% rename from internal/processing/istio/virtual_service_processor_test.go rename to internal/processing/processors/istio/virtual_service_processor_test.go index 931205005..952f49146 100644 --- a/internal/processing/istio/virtual_service_processor_test.go +++ b/internal/processing/processors/istio/virtual_service_processor_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/kyma-project/api-gateway/internal/processing" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/istio" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rulev1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" diff --git a/internal/processing/ory/access_rule_processor.go b/internal/processing/processors/ory/access_rule_processor.go similarity index 100% rename from internal/processing/ory/access_rule_processor.go rename to internal/processing/processors/ory/access_rule_processor.go diff --git a/internal/processing/ory/access_rule_processor_test.go b/internal/processing/processors/ory/access_rule_processor_test.go similarity index 99% rename from internal/processing/ory/access_rule_processor_test.go rename to internal/processing/processors/ory/access_rule_processor_test.go index 7cb8f9a44..48c7711d9 100644 --- a/internal/processing/ory/access_rule_processor_test.go +++ b/internal/processing/processors/ory/access_rule_processor_test.go @@ -8,8 +8,7 @@ import ( "strconv" "github.com/kyma-project/api-gateway/internal/processing" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/ory" + "github.com/kyma-project/api-gateway/internal/processing/processors/ory" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" @@ -19,6 +18,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" ) var _ = Describe("Access Rule Processor", func() { diff --git a/internal/processing/ory/authorization_policy_processor.go b/internal/processing/processors/ory/authorization_policy_processor.go similarity index 100% rename from internal/processing/ory/authorization_policy_processor.go rename to internal/processing/processors/ory/authorization_policy_processor.go diff --git a/internal/processing/ory/ory_suite_test.go b/internal/processing/processors/ory/ory_suite_test.go similarity index 100% rename from internal/processing/ory/ory_suite_test.go rename to internal/processing/processors/ory/ory_suite_test.go diff --git a/internal/processing/ory/reconciliation.go b/internal/processing/processors/ory/reconciliation.go similarity index 83% rename from internal/processing/ory/reconciliation.go rename to internal/processing/processors/ory/reconciliation.go index 17797f5b2..6590a9679 100644 --- a/internal/processing/ory/reconciliation.go +++ b/internal/processing/processors/ory/reconciliation.go @@ -2,13 +2,14 @@ package ory import ( "context" - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - "github.com/go-logr/logr" + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" "github.com/kyma-project/api-gateway/internal/processing" "github.com/kyma-project/api-gateway/internal/validation" networkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + + oryValidation "github.com/kyma-project/api-gateway/internal/validation/v1beta1/ory" ) type Reconciliation struct { @@ -34,12 +35,9 @@ func (r Reconciliation) Validate(ctx context.Context, client client.Client, apiR return make([]validation.Failure, 0), err } - validator := validation.APIRuleValidator{ - HandlerValidator: &handlerValidator{}, - AccessStrategiesValidator: &asValidator{}, - DefaultDomainName: r.config.DefaultDomainName, - } - return validator.Validate(ctx, client, apiRule, vsList), nil + validator := oryValidation.NewAPIRuleValidator(apiRule, r.config.DefaultDomainName) + + return validator.Validate(ctx, client, vsList), nil } func (r Reconciliation) GetProcessors() []processing.ReconciliationProcessor { diff --git a/internal/processing/ory/reconciliation_test.go b/internal/processing/processors/ory/reconciliation_test.go similarity index 96% rename from internal/processing/ory/reconciliation_test.go rename to internal/processing/processors/ory/reconciliation_test.go index cc2bbb74c..bdb899ef9 100644 --- a/internal/processing/ory/reconciliation_test.go +++ b/internal/processing/processors/ory/reconciliation_test.go @@ -5,8 +5,8 @@ import ( "fmt" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/ory" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/ory" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rulev1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" diff --git a/internal/processing/ory/request_authentication_processor.go b/internal/processing/processors/ory/request_authentication_processor.go similarity index 100% rename from internal/processing/ory/request_authentication_processor.go rename to internal/processing/processors/ory/request_authentication_processor.go diff --git a/internal/processing/processors/ory/status.go b/internal/processing/processors/ory/status.go new file mode 100644 index 000000000..0a03800a9 --- /dev/null +++ b/internal/processing/processors/ory/status.go @@ -0,0 +1,25 @@ +package ory + +import ( + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/processing/status" + v1beta1Status "github.com/kyma-project/api-gateway/internal/processing/status/v1beta1" +) + +func (r Reconciliation) GetStatusBase(statusCode string) status.ReconciliationStatusVisitor { + return StatusBase(statusCode) +} + +func StatusBase(statusCode string) status.ReconciliationStatusVisitor { + return v1beta1Status.ReconciliationV1beta1Status{ + ApiRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusCode(statusCode), + }, + VirtualServiceStatus: &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusCode(statusCode), + }, + AccessRuleStatus: &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusCode(statusCode), + }, + } +} diff --git a/internal/processing/ory/status_test.go b/internal/processing/processors/ory/status_test.go similarity index 76% rename from internal/processing/ory/status_test.go rename to internal/processing/processors/ory/status_test.go index 4a029e13b..de5ab408d 100644 --- a/internal/processing/ory/status_test.go +++ b/internal/processing/processors/ory/status_test.go @@ -2,6 +2,7 @@ package ory import ( gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + v1beta1Status "github.com/kyma-project/api-gateway/internal/processing/status/v1beta1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -9,7 +10,7 @@ import ( var _ = Describe("OryStatusBase", func() { It("should create status base with AP and RA set to nil", func() { // when - status := StatusBase(gatewayv1beta1.StatusSkipped) + status := StatusBase(string(gatewayv1beta1.StatusSkipped)).(v1beta1Status.ReconciliationV1beta1Status) Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusSkipped)) Expect(status.AccessRuleStatus.Code).To(Equal(gatewayv1beta1.StatusSkipped)) diff --git a/internal/processing/ory/virtual_service_processor.go b/internal/processing/processors/ory/virtual_service_processor.go similarity index 100% rename from internal/processing/ory/virtual_service_processor.go rename to internal/processing/processors/ory/virtual_service_processor.go diff --git a/internal/processing/ory/virtual_service_processor_test.go b/internal/processing/processors/ory/virtual_service_processor_test.go similarity index 99% rename from internal/processing/ory/virtual_service_processor_test.go rename to internal/processing/processors/ory/virtual_service_processor_test.go index 2fa0ca5fe..e4a64fc83 100644 --- a/internal/processing/ory/virtual_service_processor_test.go +++ b/internal/processing/processors/ory/virtual_service_processor_test.go @@ -11,8 +11,8 @@ import ( "time" "github.com/kyma-project/api-gateway/internal/processing" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" - "github.com/kyma-project/api-gateway/internal/processing/ory" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" + "github.com/kyma-project/api-gateway/internal/processing/processors/ory" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rulev1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" diff --git a/internal/processing/processors/v2alpha1/status.go b/internal/processing/processors/v2alpha1/status.go new file mode 100644 index 000000000..7f7c9d8ed --- /dev/null +++ b/internal/processing/processors/v2alpha1/status.go @@ -0,0 +1,13 @@ +package v2alpha1 + +import ( + "github.com/kyma-project/api-gateway/apis/gateway/v2alpha1" + v1alpha2Status "github.com/kyma-project/api-gateway/internal/processing/status/v2alpha1" +) + +func Base(state string) v1alpha2Status.ReconciliationV2alpha1Status { + return v1alpha2Status.ReconciliationV2alpha1Status{ + State: v2alpha1.State(state), + Description: "", + } +} diff --git a/internal/processing/processors/v2alpha1/v2alpha1.go b/internal/processing/processors/v2alpha1/v2alpha1.go new file mode 100644 index 000000000..45d90d218 --- /dev/null +++ b/internal/processing/processors/v2alpha1/v2alpha1.go @@ -0,0 +1,60 @@ +package v2alpha1 + +import ( + "context" + "github.com/go-logr/logr" + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/processing" + "github.com/kyma-project/api-gateway/internal/processing/processors/istio" + "github.com/kyma-project/api-gateway/internal/processing/status" + "github.com/kyma-project/api-gateway/internal/validation" + v2alpha1Validation "github.com/kyma-project/api-gateway/internal/validation/v2alpha1" + networkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Reconciliation struct { + processors []processing.ReconciliationProcessor + config processing.ReconciliationConfig +} + +func (r Reconciliation) Validate(ctx context.Context, client client.Client, apiRule *gatewayv1beta1.APIRule) ([]validation.Failure, error) { + + var vsList networkingv1beta1.VirtualServiceList + if err := client.List(ctx, &vsList); err != nil { + return make([]validation.Failure, 0), err + } + + validator := v2alpha1Validation.APIRuleValidator{ + //HandlerValidator: &handlerValidator{}, + //InjectionValidator: &injectionValidator{ctx: ctx, client: client}, + DefaultDomainName: r.config.DefaultDomainName, + } + return validator.Validate(ctx, client, vsList), nil +} + +func (r Reconciliation) GetStatusBase(statusCode string) status.ReconciliationStatusVisitor { + return Base(statusCode) +} + +func (r Reconciliation) GetProcessors() []processing.ReconciliationProcessor { + return r.processors +} + +func NewReconciliation(config processing.ReconciliationConfig, log *logr.Logger) Reconciliation { + vsProcessor := istio.NewVirtualServiceProcessor(config) + apProcessor := istio.NewAuthorizationPolicyProcessor(config, log) + raProcessor := istio.NewRequestAuthenticationProcessor(config) + + return Reconciliation{ + processors: []processing.ReconciliationProcessor{vsProcessor, raProcessor, apProcessor}, + config: config, + } +} + +type handlerValidator struct{} + +type injectionValidator struct { + ctx context.Context + client client.Client +} diff --git a/internal/processing/processors/virtual_service_processor_test.go b/internal/processing/processors/virtual_service_processor_test.go index a0d98acc8..c83469392 100644 --- a/internal/processing/processors/virtual_service_processor_test.go +++ b/internal/processing/processors/virtual_service_processor_test.go @@ -8,7 +8,7 @@ import ( "github.com/kyma-project/api-gateway/internal/builders" "github.com/kyma-project/api-gateway/internal/processing" - . "github.com/kyma-project/api-gateway/internal/processing/internal/test" + . "github.com/kyma-project/api-gateway/internal/processing/processing_test" "github.com/kyma-project/api-gateway/internal/processing/processors" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/internal/processing/reconciliation.go b/internal/processing/reconciliation.go index a9d67a510..7edb7e33e 100644 --- a/internal/processing/reconciliation.go +++ b/internal/processing/reconciliation.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/processing/status" "github.com/go-logr/logr" "github.com/kyma-project/api-gateway/internal/validation" @@ -16,8 +17,8 @@ type ReconciliationCommand interface { // Validate performs provided APIRule validation in context of the provided client cluster Validate(context.Context, client.Client, *gatewayv1beta1.APIRule) ([]validation.Failure, error) - // GetStatusBase returns ReconciliationStatus that sets unused subresources status to nil and to gatewayv1beta1.StatusCode paramter for all the others - GetStatusBase(gatewayv1beta1.StatusCode) ReconciliationStatus + // GetStatusBase returns ReconciliationV1beta1Status that sets unused subresources status to nil and to gatewayv1beta1.StatusCode paramter for all the others + GetStatusBase(string) status.ReconciliationStatusVisitor // GetProcessors returns the processor relevant for the reconciliation of this command. GetProcessors() []ReconciliationProcessor @@ -30,22 +31,21 @@ type ReconciliationProcessor interface { } // Reconcile executes the reconciliation of the APIRule using the given reconciliation command. -func Reconcile(ctx context.Context, client client.Client, log *logr.Logger, cmd ReconciliationCommand, apiRule *gatewayv1beta1.APIRule) ReconciliationStatus { - +func Reconcile(ctx context.Context, client client.Client, log *logr.Logger, cmd ReconciliationCommand, apiRule *gatewayv1beta1.APIRule) status.ReconciliationStatusVisitor { validationFailures, err := cmd.Validate(ctx, client, apiRule) if err != nil { // We set the status to skipped because it was not the validation that failed, but an error occurred during validation. log.Error(err, "Error during validation") - statusBase := cmd.GetStatusBase(gatewayv1beta1.StatusSkipped) - errorMap := map[ResourceSelector][]error{OnApiRule: {err}} - return GetStatusForErrorMap(errorMap, statusBase) + statusBase := cmd.GetStatusBase(string(gatewayv1beta1.StatusSkipped)) + errorMap := map[status.ResourceSelector][]error{status.OnApiRule: {err}} + return statusBase.GetStatusForErrorMap(errorMap) } if len(validationFailures) > 0 { failuresJson, _ := json.Marshal(validationFailures) log.Info(fmt.Sprintf(`Validation failure {"controller": "Api", "request": "%s/%s", "failures": %s}`, apiRule.Namespace, apiRule.Name, string(failuresJson))) - statusBase := cmd.GetStatusBase(gatewayv1beta1.StatusSkipped) - return GenerateStatusFromFailures(validationFailures, statusBase) + statusBase := cmd.GetStatusBase(string(gatewayv1beta1.StatusSkipped)) + return statusBase.GenerateStatusFromFailures(validationFailures) } for _, processor := range cmd.GetProcessors() { @@ -53,28 +53,28 @@ func Reconcile(ctx context.Context, client client.Client, log *logr.Logger, cmd objectChanges, err := processor.EvaluateReconciliation(ctx, client, apiRule) if err != nil { log.Error(err, "Error during reconciliation") - statusBase := cmd.GetStatusBase(gatewayv1beta1.StatusSkipped) - errorMap := map[ResourceSelector][]error{OnApiRule: {err}} - return GetStatusForErrorMap(errorMap, statusBase) + statusBase := cmd.GetStatusBase(string(gatewayv1beta1.StatusSkipped)) + errorMap := map[status.ResourceSelector][]error{status.OnApiRule: {err}} + return statusBase.GetStatusForErrorMap(errorMap) } errorMap := applyChanges(ctx, client, objectChanges...) if len(errorMap) > 0 { log.Error(err, "Error during applying reconciliation") - statusBase := cmd.GetStatusBase(gatewayv1beta1.StatusOK) - return GetStatusForErrorMap(errorMap, statusBase) + statusBase := cmd.GetStatusBase(string(gatewayv1beta1.StatusOK)) + return statusBase.GetStatusForErrorMap(errorMap) } } - statusBase := cmd.GetStatusBase(gatewayv1beta1.StatusOK) - return GenerateStatusFromFailures([]validation.Failure{}, statusBase) + statusBase := cmd.GetStatusBase(string(gatewayv1beta1.StatusOK)) + return statusBase.GenerateStatusFromFailures([]validation.Failure{}) } // applyChanges applies the given commands on the cluster // returns map of errors that happened for all subresources // the map is empty if no error happened -func applyChanges(ctx context.Context, client client.Client, changes ...*ObjectChange) map[ResourceSelector][]error { - errorMap := make(map[ResourceSelector][]error) +func applyChanges(ctx context.Context, client client.Client, changes ...*ObjectChange) map[status.ResourceSelector][]error { + errorMap := make(map[status.ResourceSelector][]error) for _, change := range changes { res, err := applyChange(ctx, client, change) if err != nil { @@ -85,7 +85,7 @@ func applyChanges(ctx context.Context, client client.Client, changes ...*ObjectC return errorMap } -func applyChange(ctx context.Context, client client.Client, change *ObjectChange) (ResourceSelector, error) { +func applyChange(ctx context.Context, client client.Client, change *ObjectChange) (status.ResourceSelector, error) { var err error switch change.Action { @@ -106,18 +106,18 @@ func applyChange(ctx context.Context, client client.Client, change *ObjectChange return objectToSelector(change.Obj), nil } -func objectToSelector(obj client.Object) ResourceSelector { +func objectToSelector(obj client.Object) status.ResourceSelector { kind := obj.GetObjectKind().GroupVersionKind().Kind switch kind { - case OnVirtualService.String(): - return OnVirtualService - case OnAccessRule.String(): - return OnAccessRule - case OnRequestAuthentication.String(): - return OnRequestAuthentication - case OnAuthorizationPolicy.String(): - return OnAuthorizationPolicy + case status.OnVirtualService.String(): + return status.OnVirtualService + case status.OnAccessRule.String(): + return status.OnAccessRule + case status.OnRequestAuthentication.String(): + return status.OnRequestAuthentication + case status.OnAuthorizationPolicy.String(): + return status.OnAuthorizationPolicy default: - return OnApiRule + return status.OnApiRule } } diff --git a/internal/processing/reconciliation_test.go b/internal/processing/reconciliation_test.go index 5b3cb07f0..e9364e6ab 100644 --- a/internal/processing/reconciliation_test.go +++ b/internal/processing/reconciliation_test.go @@ -4,11 +4,12 @@ import ( "context" "fmt" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/processing/status" "github.com/go-logr/logr" "github.com/kyma-project/api-gateway/internal/builders" "github.com/kyma-project/api-gateway/internal/processing" - oryHandler "github.com/kyma-project/api-gateway/internal/processing/ory" + oryHandler "github.com/kyma-project/api-gateway/internal/processing/processors/ory" "github.com/kyma-project/api-gateway/internal/validation" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -17,6 +18,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + v1beta1Status "github.com/kyma-project/api-gateway/internal/processing/status/v1beta1" ) var _ = Describe("Reconcile", func() { @@ -24,14 +27,14 @@ var _ = Describe("Reconcile", func() { // given cmd := MockReconciliationCommand{ validateMock: func() ([]validation.Failure, error) { return nil, fmt.Errorf("error during validation") }, - getStatusBaseMock: func() processing.ReconciliationStatus { + getStatusBaseMock: func() status.ReconciliationStatusVisitor { return mockStatusBase(gatewayv1beta1.StatusSkipped) }, } client := fake.NewClientBuilder().Build() // when - status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}) + status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}).(v1beta1Status.ReconciliationV1beta1Status) // then Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusError)) @@ -50,14 +53,14 @@ var _ = Describe("Reconcile", func() { }} cmd := MockReconciliationCommand{ validateMock: func() ([]validation.Failure, error) { return failures, nil }, - getStatusBaseMock: func() processing.ReconciliationStatus { + getStatusBaseMock: func() status.ReconciliationStatusVisitor { return mockStatusBase(gatewayv1beta1.StatusSkipped) }, } client := fake.NewClientBuilder().Build() // when - status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}) + status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}).(v1beta1Status.ReconciliationV1beta1Status) // then Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusError)) @@ -80,7 +83,7 @@ var _ = Describe("Reconcile", func() { cmd := MockReconciliationCommand{ validateMock: func() ([]validation.Failure, error) { return []validation.Failure{}, nil }, processorMocks: func() []processing.ReconciliationProcessor { return []processing.ReconciliationProcessor{p} }, - getStatusBaseMock: func() processing.ReconciliationStatus { + getStatusBaseMock: func() status.ReconciliationStatusVisitor { return mockStatusBase(gatewayv1beta1.StatusSkipped) }, } @@ -88,7 +91,7 @@ var _ = Describe("Reconcile", func() { client := fake.NewClientBuilder().Build() // when - status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}) + status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}).(v1beta1Status.ReconciliationV1beta1Status) // then Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusError)) @@ -112,7 +115,7 @@ var _ = Describe("Reconcile", func() { cmd := MockReconciliationCommand{ validateMock: func() ([]validation.Failure, error) { return []validation.Failure{}, nil }, processorMocks: func() []processing.ReconciliationProcessor { return []processing.ReconciliationProcessor{p} }, - getStatusBaseMock: func() processing.ReconciliationStatus { + getStatusBaseMock: func() status.ReconciliationStatusVisitor { return mockStatusBase(gatewayv1beta1.StatusOK) }, } @@ -120,7 +123,7 @@ var _ = Describe("Reconcile", func() { client := fake.NewClientBuilder().Build() // when - status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}) + status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}).(v1beta1Status.ReconciliationV1beta1Status) // then Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusError)) @@ -151,7 +154,7 @@ var _ = Describe("Reconcile", func() { cmd := MockReconciliationCommand{ validateMock: func() ([]validation.Failure, error) { return []validation.Failure{}, nil }, processorMocks: func() []processing.ReconciliationProcessor { return []processing.ReconciliationProcessor{p} }, - getStatusBaseMock: func() processing.ReconciliationStatus { + getStatusBaseMock: func() status.ReconciliationStatusVisitor { return mockStatusBase(gatewayv1beta1.StatusOK) }, } @@ -162,7 +165,7 @@ var _ = Describe("Reconcile", func() { client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(toBeUpdatedVs, toBeDeletedVs).Build() // when - status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}) + status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}).(v1beta1Status.ReconciliationV1beta1Status) // then Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusOK)) @@ -189,7 +192,7 @@ var _ = Describe("Reconcile", func() { cmd := MockReconciliationCommand{ validateMock: func() ([]validation.Failure, error) { return []validation.Failure{}, nil }, processorMocks: func() []processing.ReconciliationProcessor { return []processing.ReconciliationProcessor{p} }, - getStatusBaseMock: func() processing.ReconciliationStatus { + getStatusBaseMock: func() status.ReconciliationStatusVisitor { return mockStatusBase(gatewayv1beta1.StatusOK) }, } @@ -200,7 +203,7 @@ var _ = Describe("Reconcile", func() { client := fake.NewClientBuilder().WithScheme(scheme).Build() // when - status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}) + status := processing.Reconcile(context.TODO(), client, testLogger(), cmd, &gatewayv1beta1.APIRule{}).(v1beta1Status.ReconciliationV1beta1Status) // then Expect(status.ApiRuleStatus.Code).To(Equal(gatewayv1beta1.StatusError)) @@ -215,7 +218,7 @@ var _ = Describe("Reconcile", func() { type MockReconciliationCommand struct { validateMock func() ([]validation.Failure, error) - getStatusBaseMock func() processing.ReconciliationStatus + getStatusBaseMock func() status.ReconciliationStatusVisitor processorMocks func() []processing.ReconciliationProcessor } @@ -235,8 +238,8 @@ func (r MockReconciliationProcessor) EvaluateReconciliation(_ context.Context, _ return r.evaluate() } -func (c MockReconciliationCommand) GetStatusBase(_ gatewayv1beta1.StatusCode) processing.ReconciliationStatus { - return c.getStatusBaseMock() +func (r MockReconciliationCommand) GetStatusBase(string) status.ReconciliationStatusVisitor { + return r.getStatusBaseMock() } func testLogger() *logr.Logger { @@ -244,6 +247,6 @@ func testLogger() *logr.Logger { return &logger } -func mockStatusBase(statusCode gatewayv1beta1.StatusCode) processing.ReconciliationStatus { - return oryHandler.StatusBase(statusCode) +func mockStatusBase(statusCode gatewayv1beta1.StatusCode) status.ReconciliationStatusVisitor { + return oryHandler.StatusBase(string(statusCode)) } diff --git a/internal/processing/status.go b/internal/processing/status.go deleted file mode 100644 index e3a49b39e..000000000 --- a/internal/processing/status.go +++ /dev/null @@ -1,146 +0,0 @@ -package processing - -import ( - "fmt" - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - - "github.com/kyma-project/api-gateway/internal/validation" -) - -type ReconciliationStatus struct { - ApiRuleStatus *gatewayv1beta1.APIRuleResourceStatus - VirtualServiceStatus *gatewayv1beta1.APIRuleResourceStatus - AccessRuleStatus *gatewayv1beta1.APIRuleResourceStatus - RequestAuthenticationStatus *gatewayv1beta1.APIRuleResourceStatus - AuthorizationPolicyStatus *gatewayv1beta1.APIRuleResourceStatus -} - -func (status ReconciliationStatus) HasError() bool { - if status.ApiRuleStatus != nil && status.ApiRuleStatus.Code == gatewayv1beta1.StatusError { - return true - } - if status.VirtualServiceStatus != nil && status.VirtualServiceStatus.Code == gatewayv1beta1.StatusError { - return true - } - if status.AccessRuleStatus != nil && status.AccessRuleStatus.Code == gatewayv1beta1.StatusError { - return true - } - if status.AuthorizationPolicyStatus != nil && status.AuthorizationPolicyStatus.Code == gatewayv1beta1.StatusError { - return true - } - if status.RequestAuthenticationStatus != nil && status.RequestAuthenticationStatus.Code == gatewayv1beta1.StatusError { - return true - } - return false -} - -type ResourceSelector int - -const ( - OnApiRule ResourceSelector = iota - OnVirtualService - OnAccessRule - OnAuthorizationPolicy - OnRequestAuthentication -) - -func (r ResourceSelector) String() string { - switch r { - case OnVirtualService: - return "VirtualService" - case OnAccessRule: - return "Rule" - case OnRequestAuthentication: - return "RequestAuthentication" - case OnAuthorizationPolicy: - return "AuthorizationPolicy" - default: - // If no Kind is resolved from the resource (e.g. subresource CRD is missing) - return "APIRule" - } -} - -func generateStatusFromErrors(errors []error) *gatewayv1beta1.APIRuleResourceStatus { - status := &gatewayv1beta1.APIRuleResourceStatus{} - if len(errors) == 0 { - status.Code = gatewayv1beta1.StatusOK - return status - } - status.Code = gatewayv1beta1.StatusError - status.Description = errors[0].Error() - for _, err := range errors[1:] { - status.Description = fmt.Sprintf("%s\n%s", status.Description, err.Error()) - } - return status -} - -func GetStatusForErrorMap(errorMap map[ResourceSelector][]error, statusBase ReconciliationStatus) ReconciliationStatus { - for key, val := range errorMap { - switch key { - case OnApiRule: - statusBase.ApiRuleStatus = generateStatusFromErrors(val) - case OnVirtualService: - statusBase.VirtualServiceStatus = generateStatusFromErrors(val) - case OnAccessRule: - statusBase.AccessRuleStatus = generateStatusFromErrors(val) - case OnAuthorizationPolicy: - statusBase.AuthorizationPolicyStatus = generateStatusFromErrors(val) - case OnRequestAuthentication: - statusBase.RequestAuthenticationStatus = generateStatusFromErrors(val) - } - - if key != OnApiRule { - if statusBase.ApiRuleStatus == nil || statusBase.ApiRuleStatus.Code == gatewayv1beta1.StatusOK { - statusBase.ApiRuleStatus = &gatewayv1beta1.APIRuleResourceStatus{ - Code: gatewayv1beta1.StatusError, - Description: fmt.Sprintf("Error has happened on subresource %s", key), - } - } else { - statusBase.ApiRuleStatus.Code = gatewayv1beta1.StatusError - statusBase.ApiRuleStatus.Description += fmt.Sprintf("\nError has happened on subresource %s", key) - } - } - } - - return statusBase -} - -func GenerateStatusFromFailures(failures []validation.Failure, statusBase ReconciliationStatus) ReconciliationStatus { - if len(failures) == 0 { - return statusBase - } - - statusBase.ApiRuleStatus = generateValidationStatus(failures) - return statusBase -} - -func generateValidationStatus(failures []validation.Failure) *gatewayv1beta1.APIRuleResourceStatus { - return toStatus(gatewayv1beta1.StatusError, generateValidationDescription(failures)) -} - -func generateValidationDescription(failures []validation.Failure) string { - var description string - - if len(failures) == 1 { - description = "Validation error: " - description += fmt.Sprintf("Attribute \"%s\": %s", failures[0].AttributePath, failures[0].Message) - } else { - const maxEntries = 3 - description = "Multiple validation errors: " - for i := 0; i < len(failures) && i < maxEntries; i++ { - description += fmt.Sprintf("\nAttribute \"%s\": %s", failures[i].AttributePath, failures[i].Message) - } - if len(failures) > maxEntries { - description += fmt.Sprintf("\n%d more error(s)...", len(failures)-maxEntries) - } - } - - return description -} - -func toStatus(c gatewayv1beta1.StatusCode, desc string) *gatewayv1beta1.APIRuleResourceStatus { - return &gatewayv1beta1.APIRuleResourceStatus{ - Code: c, - Description: desc, - } -} diff --git a/internal/processing/status/status.go b/internal/processing/status/status.go new file mode 100644 index 000000000..ae441f645 --- /dev/null +++ b/internal/processing/status/status.go @@ -0,0 +1,45 @@ +package status + +import ( + "github.com/kyma-project/api-gateway/apis/gateway/versions" + "github.com/kyma-project/api-gateway/internal/validation" +) + +type ReconciliationStatusVisitor interface { + VisitStatus(status Status) error + + GetStatusForErrorMap(errorMap map[ResourceSelector][]error) ReconciliationStatusVisitor + GenerateStatusFromFailures([]validation.Failure) ReconciliationStatusVisitor + + HasError() bool +} + +type Status interface { + ApiRuleStatusVersion() versions.Version +} + +type ResourceSelector int + +const ( + OnApiRule ResourceSelector = iota + OnVirtualService + OnAccessRule + OnAuthorizationPolicy + OnRequestAuthentication +) + +func (r ResourceSelector) String() string { + switch r { + case OnVirtualService: + return "VirtualService" + case OnAccessRule: + return "Rule" + case OnRequestAuthentication: + return "RequestAuthentication" + case OnAuthorizationPolicy: + return "AuthorizationPolicy" + default: + // If no Kind is resolved from the resource (e.g. subresource CRD is missing) + return "APIRule" + } +} diff --git a/internal/processing/status/status_suite_test.go b/internal/processing/status/status_suite_test.go new file mode 100644 index 000000000..ae1b294e7 --- /dev/null +++ b/internal/processing/status/status_suite_test.go @@ -0,0 +1,44 @@ +package status_test + +import ( + "fmt" + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" + "os" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestStatus(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Status Suite") +} + +var _ = ReportAfterSuite("custom reporter", func(report types.Report) { + logger := zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)) + + if key, ok := os.LookupEnv("ARTIFACTS"); ok { + reportsFilename := fmt.Sprintf("%s/%s", key, "junit-processing-status.xml") + logger.Info("Generating reports at", "location", reportsFilename) + err := reporters.GenerateJUnitReport(report, reportsFilename) + + if err != nil { + logger.Error(err, "Junit Report Generation Error") + } + } else { + if err := os.MkdirAll("../../reports", 0755); err != nil { + logger.Error(err, "could not create directory") + } + + reportsFilename := fmt.Sprintf("%s/%s", "../../reports", "junit-processing-status.xml") + logger.Info("Generating reports at", "location", reportsFilename) + err := reporters.GenerateJUnitReport(report, reportsFilename) + + if err != nil { + logger.Error(err, "Junit Report Generation Error") + } + } +}) diff --git a/internal/processing/status_test.go b/internal/processing/status/status_test.go similarity index 70% rename from internal/processing/status_test.go rename to internal/processing/status/status_test.go index 819eaa0ee..06036ae3e 100644 --- a/internal/processing/status_test.go +++ b/internal/processing/status/status_test.go @@ -1,6 +1,7 @@ -package processing +package status_test import ( + "fmt" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" "strings" @@ -60,3 +61,34 @@ var _ = Describe("Status", func() { }) }) }) + +func generateValidationStatus(failures []validation.Failure) *gatewayv1beta1.APIRuleResourceStatus { + return toStatus(gatewayv1beta1.StatusError, generateValidationDescription(failures)) +} + +func generateValidationDescription(failures []validation.Failure) string { + var description string + + if len(failures) == 1 { + description = "Validation error: " + description += fmt.Sprintf("Attribute \"%s\": %s", failures[0].AttributePath, failures[0].Message) + } else { + const maxEntries = 3 + description = "Multiple validation errors: " + for i := 0; i < len(failures) && i < maxEntries; i++ { + description += fmt.Sprintf("\nAttribute \"%s\": %s", failures[i].AttributePath, failures[i].Message) + } + if len(failures) > maxEntries { + description += fmt.Sprintf("\n%d more error(s)...", len(failures)-maxEntries) + } + } + + return description +} + +func toStatus(c gatewayv1beta1.StatusCode, desc string) *gatewayv1beta1.APIRuleResourceStatus { + return &gatewayv1beta1.APIRuleResourceStatus{ + Code: c, + Description: desc, + } +} diff --git a/internal/processing/status/v1beta1/v1beta1.go b/internal/processing/status/v1beta1/v1beta1.go new file mode 100644 index 000000000..a7198b82b --- /dev/null +++ b/internal/processing/status/v1beta1/v1beta1.go @@ -0,0 +1,136 @@ +package v1beta1 + +import ( + "fmt" + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/apis/gateway/versions" + "github.com/kyma-project/api-gateway/internal/processing/status" + "github.com/kyma-project/api-gateway/internal/validation" +) + +type ReconciliationV1beta1Status struct { + ApiRuleStatus *gatewayv1beta1.APIRuleResourceStatus + VirtualServiceStatus *gatewayv1beta1.APIRuleResourceStatus + AccessRuleStatus *gatewayv1beta1.APIRuleResourceStatus + RequestAuthenticationStatus *gatewayv1beta1.APIRuleResourceStatus + AuthorizationPolicyStatus *gatewayv1beta1.APIRuleResourceStatus +} + +func (s ReconciliationV1beta1Status) HasError() bool { + if s.ApiRuleStatus != nil && s.ApiRuleStatus.Code == gatewayv1beta1.StatusError { + return true + } + if s.VirtualServiceStatus != nil && s.VirtualServiceStatus.Code == gatewayv1beta1.StatusError { + return true + } + if s.AccessRuleStatus != nil && s.AccessRuleStatus.Code == gatewayv1beta1.StatusError { + return true + } + if s.AuthorizationPolicyStatus != nil && s.AuthorizationPolicyStatus.Code == gatewayv1beta1.StatusError { + return true + } + if s.RequestAuthenticationStatus != nil && s.RequestAuthenticationStatus.Code == gatewayv1beta1.StatusError { + return true + } + return false +} + +func (s ReconciliationV1beta1Status) GetStatusForErrorMap(errorMap map[status.ResourceSelector][]error) status.ReconciliationStatusVisitor { + for key, val := range errorMap { + switch key { + case status.OnApiRule: + s.ApiRuleStatus = generateStatusFromErrors(val) + case status.OnVirtualService: + s.VirtualServiceStatus = generateStatusFromErrors(val) + case status.OnAccessRule: + s.AccessRuleStatus = generateStatusFromErrors(val) + case status.OnAuthorizationPolicy: + s.AuthorizationPolicyStatus = generateStatusFromErrors(val) + case status.OnRequestAuthentication: + s.RequestAuthenticationStatus = generateStatusFromErrors(val) + } + + if key != status.OnApiRule { + if s.ApiRuleStatus == nil || s.ApiRuleStatus.Code == gatewayv1beta1.StatusOK { + s.ApiRuleStatus = &gatewayv1beta1.APIRuleResourceStatus{ + Code: gatewayv1beta1.StatusError, + Description: fmt.Sprintf("Error has happened on subresource %s", key), + } + } else { + s.ApiRuleStatus.Code = gatewayv1beta1.StatusError + s.ApiRuleStatus.Description += fmt.Sprintf("\nError has happened on subresource %s", key) + } + } + } + + return s +} + +func (s ReconciliationV1beta1Status) GenerateStatusFromFailures(failures []validation.Failure) status.ReconciliationStatusVisitor { + if len(failures) == 0 { + return s + } + + s.ApiRuleStatus = generateValidationStatus(failures) + return s +} + +func generateStatusFromErrors(errors []error) *gatewayv1beta1.APIRuleResourceStatus { + status := &gatewayv1beta1.APIRuleResourceStatus{} + if len(errors) == 0 { + status.Code = gatewayv1beta1.StatusOK + return status + } + status.Code = gatewayv1beta1.StatusError + status.Description = errors[0].Error() + for _, err := range errors[1:] { + status.Description = fmt.Sprintf("%s\n%s", status.Description, err.Error()) + } + return status +} + +func (s ReconciliationV1beta1Status) VisitStatus(status status.Status) error { + if status.ApiRuleStatusVersion() != versions.V1beta1 { + return fmt.Errorf("v1beta1 status visitor cannot handle status of version %s", status.ApiRuleStatusVersion()) + } + + v1beta1Status := status.(*gatewayv1beta1.APIRuleStatus) + s.ApiRuleStatus = v1beta1Status.APIRuleStatus + s.VirtualServiceStatus = v1beta1Status.VirtualServiceStatus + s.AccessRuleStatus = v1beta1Status.AccessRuleStatus + s.RequestAuthenticationStatus = v1beta1Status.RequestAuthenticationStatus + s.AuthorizationPolicyStatus = v1beta1Status.AuthorizationPolicyStatus + + return nil +} + +func generateValidationStatus(failures []validation.Failure) *gatewayv1beta1.APIRuleResourceStatus { + return toStatus(gatewayv1beta1.StatusError, generateValidationDescription(failures)) +} + +func generateValidationDescription(failures []validation.Failure) string { + var description string + + if len(failures) == 1 { + description = "Validation error: " + description += fmt.Sprintf("Attribute \"%s\": %s", failures[0].AttributePath, failures[0].Message) + } else { + const maxEntries = 3 + description = "Multiple validation errors: " + for i := 0; i < len(failures) && i < maxEntries; i++ { + description += fmt.Sprintf("\nAttribute \"%s\": %s", failures[i].AttributePath, failures[i].Message) + } + if len(failures) > maxEntries { + description += fmt.Sprintf("\n%d more error(s)...", len(failures)-maxEntries) + } + } + + return description +} + +func toStatus(c gatewayv1beta1.StatusCode, desc string) *gatewayv1beta1.APIRuleResourceStatus { + return &gatewayv1beta1.APIRuleResourceStatus{ + Code: c, + Description: desc, + } +} diff --git a/internal/processing/status/v2alpha1/v2alpha1.go b/internal/processing/status/v2alpha1/v2alpha1.go new file mode 100644 index 000000000..55fed2ed9 --- /dev/null +++ b/internal/processing/status/v2alpha1/v2alpha1.go @@ -0,0 +1,85 @@ +package v2alpha1 + +import ( + "fmt" + gatewayv2alpha1 "github.com/kyma-project/api-gateway/apis/gateway/v2alpha1" + "github.com/kyma-project/api-gateway/apis/gateway/versions" + "github.com/kyma-project/api-gateway/internal/processing/status" + "github.com/kyma-project/api-gateway/internal/validation" + "strings" +) + +type ReconciliationV2alpha1Status struct { + State gatewayv2alpha1.State + Description string +} + +// GetStatusForErrorMap combines the errors from the errorMap into a single string and sets the state to Error +// TODO: function should be extended to support conditions, when they are implemented +func (s ReconciliationV2alpha1Status) GetStatusForErrorMap(errorMap map[status.ResourceSelector][]error) status.ReconciliationStatusVisitor { + errString := strings.Builder{} + for selector, errors := range errorMap { + errString.WriteString(fmt.Sprintf("%s:", selector.String())) + for _, err := range errors { + errString.WriteString(fmt.Sprintf(" %s", err.Error())) + } + errString.WriteString("\n") + } + + s.State = gatewayv2alpha1.Error + s.Description = errString.String() + + return s +} + +func (s ReconciliationV2alpha1Status) GenerateStatusFromFailures(failures []validation.Failure) status.ReconciliationStatusVisitor { + if len(failures) == 0 { + return s + } + + errString := strings.Builder{} + for _, failure := range failures { + errString.WriteString(fmt.Sprintf("\"%s\": %s\n", failure.AttributePath, failure.Message)) + } + + s.State = gatewayv2alpha1.Error + s.Description = errString.String() + + return s +} + +func (s ReconciliationV2alpha1Status) HasError() bool { + return s.State == gatewayv2alpha1.Error +} + +func (s ReconciliationV2alpha1Status) VisitStatus(status status.Status) error { + if status.ApiRuleStatusVersion() != versions.V2alpha1 { + return fmt.Errorf("v2alpha1 status visitor cannot handle status of version %s", status.ApiRuleStatusVersion()) + } + + v2alpha1Status := status.(*gatewayv2alpha1.APIRuleStatus) + v2alpha1Status.State = s.State + v2alpha1Status.Description = s.Description + + return nil +} + +func generateValidationDescription(failures []validation.Failure) string { + var description string + + if len(failures) == 1 { + description = "Validation error: " + description += fmt.Sprintf("Attribute \"%s\": %s", failures[0].AttributePath, failures[0].Message) + } else { + const maxEntries = 3 + description = "Multiple validation errors: " + for i := 0; i < len(failures) && i < maxEntries; i++ { + description += fmt.Sprintf("\nAttribute \"%s\": %s", failures[i].AttributePath, failures[i].Message) + } + if len(failures) > maxEntries { + description += fmt.Sprintf("\n%d more error(s)...", len(failures)-maxEntries) + } + } + + return description +} diff --git a/internal/validation/dummy.go b/internal/validation/dummy.go deleted file mode 100644 index bf01d02c6..000000000 --- a/internal/validation/dummy.go +++ /dev/null @@ -1,18 +0,0 @@ -package validation - -import ( - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" -) - -// dummy is an handler validator that does nothing -type dummyHandlerValidator struct{} - -func (dummy *dummyHandlerValidator) Validate(attrPath string, handler *gatewayv1beta1.Handler) []Failure { - return nil -} - -type dummyAccessStrategiesValidator struct{} - -func (dummy *dummyAccessStrategiesValidator) Validate(attrPath string, accessStrategies []*gatewayv1beta1.Authenticator) []Failure { - return nil -} diff --git a/internal/validation/helpers.go b/internal/validation/helpers.go index 596d262d9..7eb5418ec 100644 --- a/internal/validation/helpers.go +++ b/internal/validation/helpers.go @@ -3,39 +3,12 @@ package validation import ( "bytes" "errors" - "fmt" - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" "net/url" "regexp" "k8s.io/apimachinery/pkg/runtime" ) -func hasPathAndMethodDuplicates(rules []gatewayv1beta1.Rule) bool { - duplicates := map[string]bool{} - - if len(rules) > 1 { - for _, rule := range rules { - if len(rule.Methods) > 0 { - for _, method := range rule.Methods { - tmp := fmt.Sprintf("%s:%s", rule.Path, method) - if duplicates[tmp] { - return true - } - duplicates[tmp] = true - } - } else { - if duplicates[rule.Path] { - return true - } - duplicates[rule.Path] = true - } - } - } - - return false -} - func IsInvalidURL(toTest string) (bool, error) { if len(toTest) == 0 { return true, errors.New("value is empty") diff --git a/internal/validation/injection.go b/internal/validation/injection.go new file mode 100644 index 000000000..2c6dafe93 --- /dev/null +++ b/internal/validation/injection.go @@ -0,0 +1,53 @@ +package validation + +import ( + "context" + "fmt" + apiv1beta1 "istio.io/api/type/v1beta1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const istioSidecarContainerName = "istio-proxy" + +type InjectionValidator struct { + ctx context.Context + client client.Client +} + +func NewInjectionValidator(ctx context.Context, client client.Client) *InjectionValidator { + return &InjectionValidator{ctx: ctx, client: client} +} + +func (v *InjectionValidator) Validate(attributePath string, selector *apiv1beta1.WorkloadSelector, namespace string) (problems []Failure, err error) { + if selector == nil { + problems = append(problems, Failure{ + AttributePath: attributePath + ".injection", + Message: "Service cannot have empty label selectors when the API Rule strategy is JWT", + }) + + return problems, nil + } + + var podList corev1.PodList + err = v.client.List(v.ctx, &podList, client.InNamespace(namespace), client.MatchingLabels(selector.MatchLabels)) + if err != nil { + return nil, err + } + + for _, pod := range podList.Items { + if !containsSidecar(pod) { + problems = append(problems, Failure{AttributePath: attributePath, Message: fmt.Sprintf("Pod %s/%s does not have an injected istio sidecar", pod.Namespace, pod.Name)}) + } + } + return problems, nil +} + +func containsSidecar(pod corev1.Pod) bool { + for _, container := range pod.Spec.Containers { + if container.Name == istioSidecarContainerName { + return true + } + } + return false +} diff --git a/internal/validation/access_strategy.go b/internal/validation/v1beta1/access_strategy.go similarity index 75% rename from internal/validation/access_strategy.go rename to internal/validation/v1beta1/access_strategy.go index 4131c189e..21bba0c97 100644 --- a/internal/validation/access_strategy.go +++ b/internal/validation/v1beta1/access_strategy.go @@ -1,14 +1,15 @@ -package validation +package v1beta1 import ( "fmt" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/validation" "golang.org/x/exp/slices" ) // CheckForExclusiveAccessStrategy checks if there is an access strategy that is not allowed in combination with other access strategies. -func CheckForExclusiveAccessStrategy(accessStrategies []*gatewayv1beta1.Authenticator, exclusiveAccessStrategy string, attributePath string) []Failure { +func CheckForExclusiveAccessStrategy(accessStrategies []*gatewayv1beta1.Authenticator, exclusiveAccessStrategy string, attributePath string) []validation.Failure { if len(accessStrategies) <= 1 { return nil @@ -20,14 +21,14 @@ func CheckForExclusiveAccessStrategy(accessStrategies []*gatewayv1beta1.Authenti if handlerIndex > -1 { path := fmt.Sprintf("%s[%d]%s", attributePath+".accessStrategies", handlerIndex, ".handler") - return []Failure{{AttributePath: path, Message: fmt.Sprintf("%s access strategy is not allowed in combination with other access strategies", exclusiveAccessStrategy)}} + return []validation.Failure{{AttributePath: path, Message: fmt.Sprintf("%s access strategy is not allowed in combination with other access strategies", exclusiveAccessStrategy)}} } return nil } // CheckForSecureAndUnsecureAccessStrategies checks if there are secure and unsecure access strategies used at the same time. -func CheckForSecureAndUnsecureAccessStrategies(accessStrategies []*gatewayv1beta1.Authenticator, attributePath string) []Failure { +func CheckForSecureAndUnsecureAccessStrategies(accessStrategies []*gatewayv1beta1.Authenticator, attributePath string) []validation.Failure { var containsSecureAccessStrategy, containsUnsecureAccessStrategy bool for _, r := range accessStrategies { @@ -40,7 +41,7 @@ func CheckForSecureAndUnsecureAccessStrategies(accessStrategies []*gatewayv1beta } if containsSecureAccessStrategy && containsUnsecureAccessStrategy { - return []Failure{{AttributePath: attributePath, Message: "Secure access strategies cannot be used in combination with unsecure access strategies"}} + return []validation.Failure{{AttributePath: attributePath, Message: "Secure access strategies cannot be used in combination with unsecure access strategies"}} } return nil diff --git a/internal/validation/access_strategy_test.go b/internal/validation/v1beta1/access_strategy_test.go similarity index 99% rename from internal/validation/access_strategy_test.go rename to internal/validation/v1beta1/access_strategy_test.go index d9f8be722..172b7ff81 100644 --- a/internal/validation/access_strategy_test.go +++ b/internal/validation/v1beta1/access_strategy_test.go @@ -1,4 +1,4 @@ -package validation_test +package v1beta1_test import ( gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" diff --git a/internal/validation/v1beta1/dummy.go b/internal/validation/v1beta1/dummy.go new file mode 100644 index 000000000..f8dd52f05 --- /dev/null +++ b/internal/validation/v1beta1/dummy.go @@ -0,0 +1,19 @@ +package v1beta1 + +import ( + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/validation" +) + +// dummy is a handler validator that does nothing +type dummyHandlerValidator struct{} + +func (dummy *dummyHandlerValidator) Validate(_ string, _ *gatewayv1beta1.Handler) []validation.Failure { + return nil +} + +type dummyAccessStrategiesValidator struct{} + +func (dummy *dummyAccessStrategiesValidator) Validate(_ string, _ []*gatewayv1beta1.Authenticator) []validation.Failure { + return nil +} diff --git a/internal/processing/istio/as_validator.go b/internal/validation/v1beta1/istio/as_validator.go similarity index 59% rename from internal/processing/istio/as_validator.go rename to internal/validation/v1beta1/istio/as_validator.go index 38d8d3be0..e1ad9c031 100644 --- a/internal/processing/istio/as_validator.go +++ b/internal/validation/v1beta1/istio/as_validator.go @@ -2,19 +2,20 @@ package istio import ( gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/validation/v1beta1" "github.com/kyma-project/api-gateway/internal/validation" ) -type asValidator struct{} +type AccessStrategyValidator struct{} var exclusiveAccessStrategies = []string{gatewayv1beta1.AccessStrategyAllow, gatewayv1beta1.AccessStrategyNoAuth, gatewayv1beta1.AccessStrategyJwt, gatewayv1beta1.AccessStrategyNoop} -func (o *asValidator) Validate(attributePath string, accessStrategies []*gatewayv1beta1.Authenticator) []validation.Failure { +func (o *AccessStrategyValidator) Validate(attributePath string, accessStrategies []*gatewayv1beta1.Authenticator) []validation.Failure { var problems []validation.Failure for _, strategy := range exclusiveAccessStrategies { - validationProblems := validation.CheckForExclusiveAccessStrategy(accessStrategies, strategy, attributePath) + validationProblems := v1beta1.CheckForExclusiveAccessStrategy(accessStrategies, strategy, attributePath) problems = append(problems, validationProblems...) } diff --git a/internal/processing/istio/as_validator_test.go b/internal/validation/v1beta1/istio/as_validator_test.go similarity index 91% rename from internal/processing/istio/as_validator_test.go rename to internal/validation/v1beta1/istio/as_validator_test.go index 3a456c54b..274ba4c90 100644 --- a/internal/processing/istio/as_validator_test.go +++ b/internal/validation/v1beta1/istio/as_validator_test.go @@ -18,7 +18,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(0)) @@ -43,7 +43,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(1)) @@ -76,7 +76,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(3)) @@ -103,7 +103,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(0)) diff --git a/internal/validation/v1beta1/istio/istio.go b/internal/validation/v1beta1/istio/istio.go new file mode 100644 index 000000000..f642ce1a0 --- /dev/null +++ b/internal/validation/v1beta1/istio/istio.go @@ -0,0 +1,22 @@ +package istio + +import ( + "context" + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/validation" + "github.com/kyma-project/api-gateway/internal/validation/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func NewAPIRuleValidator(ctx context.Context, client client.Client, api *gatewayv1beta1.APIRule, defaultDomainName string) validation.ApiRuleValidator { + return &v1beta1.APIRuleValidator{ + Api: api, + + HandlerValidator: &HandlerValidator{}, + AccessStrategiesValidator: &AccessStrategyValidator{}, + MutatorsValidator: &MutatorsValidator{}, + InjectionValidator: validation.NewInjectionValidator(ctx, client), + RulesValidator: &RulesValidator{}, + DefaultDomainName: defaultDomainName, + } +} diff --git a/internal/processing/istio/jwt_validator.go b/internal/validation/v1beta1/istio/jwt_validator.go similarity index 85% rename from internal/processing/istio/jwt_validator.go rename to internal/validation/v1beta1/istio/jwt_validator.go index 6aa2f44c6..1ca651dde 100644 --- a/internal/processing/istio/jwt_validator.go +++ b/internal/validation/v1beta1/istio/jwt_validator.go @@ -1,7 +1,6 @@ package istio import ( - "context" "encoding/json" "errors" "fmt" @@ -10,18 +9,11 @@ import ( oryjwt "github.com/kyma-project/api-gateway/internal/types/ory" "github.com/kyma-project/api-gateway/internal/validation" - apiv1beta1 "istio.io/api/type/v1beta1" - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) -const ( - istioSidecarContainerName string = "istio-proxy" -) - -type handlerValidator struct{} +type HandlerValidator struct{} -func (o *handlerValidator) Validate(attributePath string, handler *gatewayv1beta1.Handler) []validation.Failure { +func (o *HandlerValidator) Validate(attributePath string, handler *gatewayv1beta1.Handler) []validation.Failure { var failures []validation.Failure var template gatewayv1beta1.JwtConfig @@ -177,48 +169,10 @@ func hasInvalidAuthorizations(attributePath string, authorizations []*gatewayv1b return } -type injectionValidator struct { - ctx context.Context - client client.Client -} - -func (v *injectionValidator) Validate(attributePath string, selector *apiv1beta1.WorkloadSelector, namespace string) (problems []validation.Failure, err error) { - if selector == nil { - problems = append(problems, validation.Failure{ - AttributePath: attributePath + ".injection", - Message: "Service cannot have empty label selectors when the API Rule strategy is JWT", - }) - - return problems, nil - } - - var podList corev1.PodList - err = v.client.List(v.ctx, &podList, client.InNamespace(namespace), client.MatchingLabels(selector.MatchLabels)) - if err != nil { - return nil, err - } - - for _, pod := range podList.Items { - if !containsSidecar(pod) { - problems = append(problems, validation.Failure{AttributePath: attributePath, Message: fmt.Sprintf("Pod %s/%s does not have an injected istio sidecar", pod.Namespace, pod.Name)}) - } - } - return problems, nil -} - -func containsSidecar(pod corev1.Pod) bool { - for _, container := range pod.Spec.Containers { - if container.Name == istioSidecarContainerName { - return true - } - } - return false -} - -type rulesValidator struct { +type RulesValidator struct { } -func (v *rulesValidator) Validate(attrPath string, rules []gatewayv1beta1.Rule) []validation.Failure { +func (v *RulesValidator) Validate(attrPath string, rules []gatewayv1beta1.Rule) []validation.Failure { var failures []validation.Failure jwtAuths := map[string]*gatewayv1beta1.JwtAuthentication{} for i, rule := range rules { diff --git a/internal/processing/istio/jwt_validator_test.go b/internal/validation/v1beta1/istio/jwt_validator_test.go similarity index 91% rename from internal/processing/istio/jwt_validator_test.go rename to internal/validation/v1beta1/istio/jwt_validator_test.go index 6dc3fd9c3..4561b56a3 100644 --- a/internal/processing/istio/jwt_validator_test.go +++ b/internal/validation/v1beta1/istio/jwt_validator_test.go @@ -5,7 +5,7 @@ import ( "encoding/json" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - processingtest "github.com/kyma-project/api-gateway/internal/processing/internal/test" + processingtest "github.com/kyma-project/api-gateway/internal/processing/processing_test" "istio.io/api/type/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -44,7 +44,7 @@ var _ = Describe("JWT Handler validation", func() { Expect(err).NotTo(HaveOccurred()) //when - problems, err := (&injectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test"}}, "default") + problems, err := (&InjectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test"}}, "default") Expect(err).NotTo(HaveOccurred()) //then @@ -53,7 +53,7 @@ var _ = Describe("JWT Handler validation", func() { It("Should fail when the workload selector is nil", func() { //when - problems, err := (&injectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", nil, "default") + problems, err := (&InjectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", nil, "default") Expect(err).NotTo(HaveOccurred()) //then @@ -76,7 +76,7 @@ var _ = Describe("JWT Handler validation", func() { Expect(err).NotTo(HaveOccurred()) //when - problems, err := (&injectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test"}}, "test") + problems, err := (&InjectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test"}}, "test") Expect(err).NotTo(HaveOccurred()) //then @@ -99,7 +99,7 @@ var _ = Describe("JWT Handler validation", func() { Expect(err).NotTo(HaveOccurred()) //when - problems, err := (&injectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test"}}, "default") + problems, err := (&InjectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test"}}, "default") Expect(err).NotTo(HaveOccurred()) //then @@ -127,7 +127,7 @@ var _ = Describe("JWT Handler validation", func() { Expect(err).NotTo(HaveOccurred()) //when - problems, err := (&injectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test-injected"}}, "default") + problems, err := (&InjectionValidator{ctx: context.TODO(), client: k8sfakeClient}).Validate("some.attribute", &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"app": "test-injected"}}, "default") Expect(err).NotTo(HaveOccurred()) //then @@ -140,7 +140,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: emptyJWTIstioConfig()} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -154,7 +154,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("no_url", "the_issuer")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -167,7 +167,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("", "")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(2)) @@ -182,7 +182,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("http://issuer.test/.well-known/jwks.json", "://example")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -195,7 +195,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("http://issuer.test/.well-known/jwks.json", "https://issuer.example.com")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -206,7 +206,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("http://issuer.test/.well-known/jwks.json", "testing@secure.istio.io")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -218,7 +218,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("http://issuer.test/.well-known/jwks.json", "http://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -229,7 +229,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("file://.well-known/jwks.json", "https://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -240,7 +240,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: jwtIstioConfig("https://issuer.test/.well-known/jwks.json", "https://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -251,7 +251,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: &runtime.RawExtension{Raw: []byte("/abc]")}} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -263,7 +263,7 @@ var _ = Describe("JWT Handler validation", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: testURLJWTOryConfig("https://issuer.test/.well-known/jwks.json", "https://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(Not(BeEmpty())) @@ -288,7 +288,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(1)) @@ -314,7 +314,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(1)) @@ -341,7 +341,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(1)) @@ -373,7 +373,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(1)) @@ -405,7 +405,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(1)) @@ -431,7 +431,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(0)) @@ -451,7 +451,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -472,7 +472,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -493,7 +493,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -512,7 +512,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(0)) @@ -534,7 +534,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(1)) @@ -555,7 +555,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(1)) @@ -576,7 +576,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(0)) @@ -595,7 +595,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&handlerValidator{}).Validate("", handler) + problems := (&HandlerValidator{}).Validate("", handler) //then Expect(problems).To(HaveLen(0)) @@ -631,7 +631,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&rulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders}) + problems := (&RulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders}) //then Expect(problems).To(HaveLen(1)) @@ -665,7 +665,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&rulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders}) + problems := (&RulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders}) //then Expect(problems).To(HaveLen(1)) @@ -712,7 +712,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&rulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders, ruleFromParams}) + problems := (&RulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders, ruleFromParams}) //then Expect(problems).To(HaveLen(1)) @@ -759,7 +759,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&rulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders, ruleFromParams}) + problems := (&RulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders, ruleFromParams}) //then Expect(problems).To(HaveLen(1)) @@ -806,7 +806,7 @@ var _ = Describe("JWT Handler validation", func() { } //when - problems := (&rulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders, ruleFromParams}) + problems := (&RulesValidator{}).Validate(".spec.rules", []gatewayv1beta1.Rule{ruleFromHeaders, ruleFromParams}) //then Expect(problems).To(HaveLen(0)) diff --git a/internal/processing/istio/mutators_validator.go b/internal/validation/v1beta1/istio/mutators_validator.go similarity index 95% rename from internal/processing/istio/mutators_validator.go rename to internal/validation/v1beta1/istio/mutators_validator.go index 83fd9b960..8a977f14b 100644 --- a/internal/processing/istio/mutators_validator.go +++ b/internal/validation/v1beta1/istio/mutators_validator.go @@ -8,12 +8,11 @@ import ( "github.com/kyma-project/api-gateway/internal/validation" ) -// mutatorsValidator is used to validate Istio-based mutator configurations. Since currently only the jwt access strategy +// MutatorsValidator is used to validate Istio-based mutator configurations. Since currently only the jwt access strategy // supports these mutators, validation is skipped for rules without jwt access strategy. -type mutatorsValidator struct { -} +type MutatorsValidator struct{} -func (m mutatorsValidator) Validate(attributePath string, rule v1beta1.Rule) []validation.Failure { +func (m MutatorsValidator) Validate(attributePath string, rule v1beta1.Rule) []validation.Failure { var failures []validation.Failure if !processing.IsJwtSecured(rule) { diff --git a/internal/processing/istio/mutators_validator_test.go b/internal/validation/v1beta1/istio/mutators_validator_test.go similarity index 89% rename from internal/processing/istio/mutators_validator_test.go rename to internal/validation/v1beta1/istio/mutators_validator_test.go index 22cf77d3d..4b9feafa4 100644 --- a/internal/processing/istio/mutators_validator_test.go +++ b/internal/validation/v1beta1/istio/mutators_validator_test.go @@ -2,7 +2,7 @@ package istio import ( "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - processingtest "github.com/kyma-project/api-gateway/internal/processing/internal/test" + processingtest "github.com/kyma-project/api-gateway/internal/processing/processing_test" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -35,7 +35,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -52,7 +52,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -71,7 +71,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -92,7 +92,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -115,7 +115,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -140,7 +140,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -165,7 +165,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(0)) @@ -182,7 +182,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -203,7 +203,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -226,7 +226,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -251,7 +251,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) @@ -276,7 +276,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&mutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(0)) @@ -305,7 +305,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&unsupportedMutator, &noNameMutator, &noConfigMutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(3)) @@ -340,7 +340,7 @@ var _ = Describe("Mutators validator", func() { rule := createJwtHandlerRule(&cookieMutator, &anotherCookieMutator) //when - problems := mutatorsValidator{}.Validate("some.attribute", rule) + problems := MutatorsValidator{}.Validate("some.attribute", rule) //then Expect(problems).To(HaveLen(1)) diff --git a/internal/validation/no_config.go b/internal/validation/v1beta1/no_config.go similarity index 58% rename from internal/validation/no_config.go rename to internal/validation/v1beta1/no_config.go index f474487ce..f3313126c 100644 --- a/internal/validation/no_config.go +++ b/internal/validation/v1beta1/no_config.go @@ -1,18 +1,19 @@ -package validation +package v1beta1 import ( "bytes" gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/validation" ) // noConfig is an accessStrategy validator that does not accept nested config type noConfigAccStrValidator struct{} -func (a *noConfigAccStrValidator) Validate(attrPath string, handler *gatewayv1beta1.Handler) []Failure { - var problems []Failure +func (a *noConfigAccStrValidator) Validate(attrPath string, handler *gatewayv1beta1.Handler) []validation.Failure { + var problems []validation.Failure if handler.Config != nil && len(handler.Config.Raw) > 0 && !bytes.Equal(handler.Config.Raw, []byte("null")) && !bytes.Equal(handler.Config.Raw, []byte("{}")) { - problems = append(problems, Failure{AttributePath: attrPath + ".config", Message: "strategy: " + handler.Name + " does not support configuration"}) + problems = append(problems, validation.Failure{AttributePath: attrPath + ".config", Message: "strategy: " + handler.Name + " does not support configuration"}) } return problems diff --git a/internal/processing/ory/as_validator.go b/internal/validation/v1beta1/ory/as_validator.go similarity index 57% rename from internal/processing/ory/as_validator.go rename to internal/validation/v1beta1/ory/as_validator.go index 4492dc916..16848e636 100644 --- a/internal/processing/ory/as_validator.go +++ b/internal/validation/v1beta1/ory/as_validator.go @@ -2,19 +2,20 @@ package ory import ( gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/validation/v1beta1" "github.com/kyma-project/api-gateway/internal/validation" ) -type asValidator struct{} +type AccessStrategyValidator struct{} var exclusiveAccessStrategies = []string{gatewayv1beta1.AccessStrategyAllow, gatewayv1beta1.AccessStrategyNoAuth, gatewayv1beta1.AccessStrategyNoop} -func (o *asValidator) Validate(attributePath string, accessStrategies []*gatewayv1beta1.Authenticator) []validation.Failure { +func (o *AccessStrategyValidator) Validate(attributePath string, accessStrategies []*gatewayv1beta1.Authenticator) []validation.Failure { var problems []validation.Failure for _, strategy := range exclusiveAccessStrategies { - validationProblems := validation.CheckForExclusiveAccessStrategy(accessStrategies, strategy, attributePath) + validationProblems := v1beta1.CheckForExclusiveAccessStrategy(accessStrategies, strategy, attributePath) problems = append(problems, validationProblems...) } diff --git a/internal/processing/ory/as_validator_test.go b/internal/validation/v1beta1/ory/as_validator_test.go similarity index 90% rename from internal/processing/ory/as_validator_test.go rename to internal/validation/v1beta1/ory/as_validator_test.go index 346caaa07..3df9281ae 100644 --- a/internal/processing/ory/as_validator_test.go +++ b/internal/validation/v1beta1/ory/as_validator_test.go @@ -18,7 +18,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(0)) @@ -42,7 +42,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(1)) @@ -74,7 +74,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(3)) @@ -101,7 +101,7 @@ var _ = Describe("AccessStrategies Istio Validator", func() { }, } //when - problems := (&asValidator{}).Validate("some.attribute", strategies) + problems := (&AccessStrategyValidator{}).Validate("some.attribute", strategies) //then Expect(problems).To(HaveLen(0)) diff --git a/internal/processing/ory/jwt_validator.go b/internal/validation/v1beta1/ory/jwt_validator.go similarity index 92% rename from internal/processing/ory/jwt_validator.go rename to internal/validation/v1beta1/ory/jwt_validator.go index 09f28c3e4..aca3719cd 100644 --- a/internal/processing/ory/jwt_validator.go +++ b/internal/validation/v1beta1/ory/jwt_validator.go @@ -9,9 +9,9 @@ import ( "github.com/kyma-project/api-gateway/internal/validation" ) -type handlerValidator struct{} +type HandlerValidator struct{} -func (o *handlerValidator) Validate(attributePath string, handler *gatewayv1beta1.Handler) []validation.Failure { +func (o *HandlerValidator) Validate(attributePath string, handler *gatewayv1beta1.Handler) []validation.Failure { var problems []validation.Failure var template ory.JWTAccStrConfig @@ -29,7 +29,7 @@ func (o *handlerValidator) Validate(attributePath string, handler *gatewayv1beta problems = append(problems, checkForIstioConfig(attributePath, handler)...) // The https:// configuration for TrustedIssuers is not necessary in terms of security best practices, - // however it is part of "secure by default" configuration, as this is the most common use case for iss claim. + // however, it is part of "secure by default" configuration, as this is the most common use case for iss claim. // If we want to allow some weaker configurations, we should have a dedicated configuration which allows that. if len(template.TrustedIssuers) > 0 { for i := 0; i < len(template.TrustedIssuers); i++ { diff --git a/internal/processing/ory/jwt_validator_test.go b/internal/validation/v1beta1/ory/jwt_validator_test.go similarity index 88% rename from internal/processing/ory/jwt_validator_test.go rename to internal/validation/v1beta1/ory/jwt_validator_test.go index 90ca232fa..d53087d22 100644 --- a/internal/processing/ory/jwt_validator_test.go +++ b/internal/validation/v1beta1/ory/jwt_validator_test.go @@ -16,7 +16,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: emptyConfig()} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -29,7 +29,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: simpleJWTConfig("a t g o")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(2)) @@ -44,7 +44,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: testURLJWTConfig("http://issuer.test/.well-known/jwks.json", "http://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -55,7 +55,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: testURLJWTConfig("file://.well-known/jwks.json", "https://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -66,7 +66,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: testURLJWTConfig("https://issuer.test/.well-known/jwks.json", "https://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -77,7 +77,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: &runtime.RawExtension{Raw: []byte("/abc]")}} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(1)) @@ -90,7 +90,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: simpleJWTConfig()} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(HaveLen(0)) @@ -100,7 +100,7 @@ var _ = Describe("JWT Validator", func() { handler := &gatewayv1beta1.Handler{Name: "jwt", Config: testURLJWTIstioConfig("https://issuer.test/.well-known/jwks.json", "https://issuer.test/")} //when - problems := (&handlerValidator{}).Validate("some.attribute", handler) + problems := (&HandlerValidator{}).Validate("some.attribute", handler) //then Expect(problems).To(Not(BeEmpty())) diff --git a/internal/validation/v1beta1/ory/ory.go b/internal/validation/v1beta1/ory/ory.go new file mode 100644 index 000000000..23703aec2 --- /dev/null +++ b/internal/validation/v1beta1/ory/ory.go @@ -0,0 +1,17 @@ +package ory + +import ( + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/validation" + "github.com/kyma-project/api-gateway/internal/validation/v1beta1" +) + +func NewAPIRuleValidator(api *gatewayv1beta1.APIRule, defaultDomainName string) validation.ApiRuleValidator { + return &v1beta1.APIRuleValidator{ + Api: api, + + HandlerValidator: &HandlerValidator{}, + AccessStrategiesValidator: &AccessStrategyValidator{}, + DefaultDomainName: defaultDomainName, + } +} diff --git a/internal/validation/v1beta1/v1beta1.go b/internal/validation/v1beta1/v1beta1.go new file mode 100644 index 000000000..bd5b944c9 --- /dev/null +++ b/internal/validation/v1beta1/v1beta1.go @@ -0,0 +1,332 @@ +package v1beta1 + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" + "github.com/kyma-project/api-gateway/internal/helpers" + "github.com/kyma-project/api-gateway/internal/processing/default_domain" + "github.com/kyma-project/api-gateway/internal/validation" + "google.golang.org/appengine/log" + networkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" + "strings" + + apiv1beta1 "istio.io/api/type/v1beta1" +) + +// APIRuleValidator is used to validate github.com/kyma-project/api-gateway/api/v1beta1/APIRule instances +type APIRuleValidator struct { + Api *gatewayv1beta1.APIRule + + HandlerValidator handlerValidator + AccessStrategiesValidator accessStrategyValidator + MutatorsValidator mutatorValidator + InjectionValidator *validation.InjectionValidator + RulesValidator rulesValidator + ServiceBlockList map[string][]string + DomainAllowList []string + HostBlockList []string + DefaultDomainName string +} + +const ( + Istio = iota + Ory +) + +type accessStrategyValidator interface { + Validate(attrPath string, accessStrategies []*gatewayv1beta1.Authenticator) []validation.Failure +} + +type mutatorValidator interface { + Validate(attrPath string, rule gatewayv1beta1.Rule) []validation.Failure +} + +type rulesValidator interface { + Validate(attrPath string, rules []gatewayv1beta1.Rule) []validation.Failure +} + +// Validate performs APIRule validation +func (v *APIRuleValidator) Validate(ctx context.Context, client client.Client, vsList networkingv1beta1.VirtualServiceList) []validation.Failure { + var failures []validation.Failure + + //Validate service on path level if it is created + if v.Api.Spec.Service != nil { + failures = append(failures, v.validateService(".spec.service", v.Api)...) + } + failures = append(failures, v.validateHost(".spec.host", vsList, v.Api)...) + failures = append(failures, v.validateRules(ctx, client, ".spec.rules", v.Api.Spec.Service == nil, v.Api)...) + + return failures +} + +func (v *APIRuleValidator) validateHost(attributePath string, vsList networkingv1beta1.VirtualServiceList, api *gatewayv1beta1.APIRule) []validation.Failure { + var problems []validation.Failure + if api.Spec.Host == nil { + problems = append(problems, validation.Failure{ + AttributePath: attributePath, + Message: "Host was nil", + }) + return problems + } + + host := *api.Spec.Host + if !default_domain.HostIncludesDomain(*api.Spec.Host) { + if v.DefaultDomainName == "" { + problems = append(problems, validation.Failure{ + AttributePath: attributePath, + Message: "Host does not contain a domain name and no default domain name is configured", + }) + } + host = default_domain.GetHostWithDefaultDomain(host, v.DefaultDomainName) + } else if len(v.DomainAllowList) > 0 { + // Do the allowList check only if the list is actually provided AND the default domain name is not used. + domainFound := false + for _, domain := range v.DomainAllowList { + // service host containing duplicated allowlisted domain should be rejected. + // for example `my-lambda.kyma.local.kyma.local` + // service host containing allowlisted domain but only as a part of bigger domain should also be rejected + // for example `my-lambda.kyma.local.com` when only `kyma.local` is allowlisted + if count := strings.Count(host, domain); count == 1 && strings.HasSuffix(host, domain) { + domainFound = true + } + } + if !domainFound { + problems = append(problems, validation.Failure{ + AttributePath: attributePath, + Message: "Host is not allowlisted", + }) + } + } + + for _, blockedHost := range v.HostBlockList { + host := *api.Spec.Host + if blockedHost == host { + subdomain := strings.Split(host, ".")[0] + problems = append(problems, validation.Failure{ + AttributePath: attributePath, + Message: fmt.Sprintf("The subdomain %s is blocklisted for %s domain", subdomain, v.DefaultDomainName), + }) + } + } + + for _, vs := range vsList.Items { + if occupiesHost(vs, host) && !ownedBy(vs, api) { + problems = append(problems, validation.Failure{ + AttributePath: attributePath, + Message: "This host is occupied by another Virtual Service", + }) + } + } + + return problems +} + +func (v *APIRuleValidator) validateService(attributePath string, api *gatewayv1beta1.APIRule) []validation.Failure { + var problems []validation.Failure + + for namespace, services := range v.ServiceBlockList { + for _, svc := range services { + serviceNamespace := helpers.FindServiceNamespace(api, nil) + if api != nil && svc == *api.Spec.Service.Name && namespace == serviceNamespace { + problems = append(problems, validation.Failure{ + AttributePath: attributePath + ".name", + Message: fmt.Sprintf("Service %s in namespace %s is blocklisted", svc, namespace), + }) + } + } + } + + return problems +} + +// Validates whether all rules are defined correctly +// Checks whether all rules have service defined for them if checkForService is true +func (v *APIRuleValidator) validateRules(ctx context.Context, client client.Client, attributePath string, checkForService bool, api *gatewayv1beta1.APIRule) []validation.Failure { + var problems []validation.Failure + + rules := api.Spec.Rules + if len(rules) == 0 { + problems = append(problems, validation.Failure{AttributePath: attributePath, Message: "No rules defined"}) + return problems + } + + if hasPathAndMethodDuplicates(rules) { + problems = append(problems, validation.Failure{AttributePath: attributePath, Message: "multiple rules defined for the same path and method"}) + } + + for i, r := range rules { + attributePathWithRuleIndex := fmt.Sprintf("%s[%d]", attributePath, i) + if checkForService && r.Service == nil { + problems = append(problems, validation.Failure{AttributePath: attributePathWithRuleIndex + ".service", Message: "No service defined with no main service on spec level"}) + } + if r.Service != nil { + labelSelector, err := helpers.GetLabelSelectorFromService(ctx, client, r.Service, api, &r) + if err != nil { + l, errorCtx := logr.FromContext(ctx) + if errorCtx != nil { + log.Errorf(ctx, "No logger in context: %s", errorCtx) + } else { + l.Info("Couldn't get label selectors for service", "error", err) + } + } + problems = append(problems, v.validateAccessStrategies(attributePathWithRuleIndex+".accessStrategies", r.AccessStrategies, labelSelector, helpers.FindServiceNamespace(api, &r))...) + for namespace, services := range v.ServiceBlockList { + for _, svc := range services { + serviceNamespace := helpers.FindServiceNamespace(api, &r) + if svc == *r.Service.Name && namespace == serviceNamespace { + problems = append(problems, validation.Failure{ + AttributePath: attributePathWithRuleIndex + ".service.name", + Message: fmt.Sprintf("Service %s in namespace %s is blocklisted", svc, namespace), + }) + } + } + } + } else if api.Spec.Service != nil { + labelSelector, err := helpers.GetLabelSelectorFromService(ctx, client, api.Spec.Service, api, nil) + if err != nil { + l, errorCtx := logr.FromContext(ctx) + if errorCtx != nil { + log.Errorf(ctx, "No logger in context: %s", errorCtx) + } else { + l.Info("Couldn't get label selectors for service", "error", err) + } + } + problems = append(problems, v.validateAccessStrategies(attributePathWithRuleIndex+".accessStrategies", r.AccessStrategies, labelSelector, helpers.FindServiceNamespace(api, &r))...) + } + + if v.MutatorsValidator != nil { + mutatorFailures := v.MutatorsValidator.Validate(attributePathWithRuleIndex, r) + problems = append(problems, mutatorFailures...) + } + + } + + if v.RulesValidator != nil { + rulesFailures := v.RulesValidator.Validate(".spec.rules", rules) + problems = append(problems, rulesFailures...) + } + + return problems +} + +func (v *APIRuleValidator) validateAccessStrategies(attributePath string, accessStrategies []*gatewayv1beta1.Authenticator, selector *apiv1beta1.WorkloadSelector, namespace string) []validation.Failure { + var problems []validation.Failure + + if len(accessStrategies) == 0 { + problems = append(problems, validation.Failure{AttributePath: attributePath, Message: "No accessStrategies defined"}) + return problems + } + + problems = append(problems, v.AccessStrategiesValidator.Validate(attributePath, accessStrategies)...) + problems = append(problems, CheckForSecureAndUnsecureAccessStrategies(accessStrategies, attributePath)...) + + for i, r := range accessStrategies { + strategyAttrPath := attributePath + fmt.Sprintf("[%d]", i) + problems = append(problems, v.validateAccessStrategy(strategyAttrPath, r, selector, namespace)...) + } + + return problems +} + +func (v *APIRuleValidator) validateAccessStrategy(attributePath string, accessStrategy *gatewayv1beta1.Authenticator, selector *apiv1beta1.WorkloadSelector, namespace string) []validation.Failure { + var problems []validation.Failure + var vld handlerValidator + + switch accessStrategy.Handler.Name { + case gatewayv1beta1.AccessStrategyAllow: + vld = vldNoConfig + case gatewayv1beta1.AccessStrategyNoAuth: + vld = vldNoConfig + case gatewayv1beta1.AccessStrategyNoop: + vld = vldNoConfig + case gatewayv1beta1.AccessStrategyUnauthorized: + vld = vldNoConfig + case gatewayv1beta1.AccessStrategyAnonymous: + vld = vldNoConfig + case gatewayv1beta1.AccessStrategyCookieSession: + vld = vldNoConfig + case gatewayv1beta1.AccessStrategyOauth2ClientCredentials: + vld = vldDummy + case gatewayv1beta1.AccessStrategyOauth2Introspection: + vld = vldDummy + case gatewayv1beta1.AccessStrategyJwt: + vld = v.HandlerValidator + if v.InjectionValidator != nil { + injectionProblems, err := v.InjectionValidator.Validate(attributePath+".injection", selector, namespace) + if err != nil { + problems = append(problems, validation.Failure{AttributePath: attributePath + ".handler", Message: fmt.Sprintf("Could not find pod for selected service, err: %s", err)}) + } else { + problems = append(problems, injectionProblems...) + } + } + default: + return []validation.Failure{{AttributePath: attributePath + ".handler", Message: fmt.Sprintf("Unsupported accessStrategy: %s", accessStrategy.Handler.Name)}} + } + + return append(problems, vld.Validate(attributePath, accessStrategy.Handler)...) +} + +func occupiesHost(vs *networkingv1beta1.VirtualService, host string) bool { + for _, h := range vs.Spec.Hosts { + if h == host { + return true + } + } + return false +} + +// Validators for AccessStrategies +var vldNoConfig = &noConfigAccStrValidator{} +var vldDummy = &dummyHandlerValidator{} + +type handlerValidator interface { + Validate(attrPath string, Handler *gatewayv1beta1.Handler) []validation.Failure +} + +func ownedBy(vs *networkingv1beta1.VirtualService, api *gatewayv1beta1.APIRule) bool { + ownerLabels := getOwnerLabels(api) + vsLabels := vs.GetLabels() + + for key, label := range ownerLabels { + val, ok := vsLabels[key] + if ok { + return val == label + } + } + return false +} + +func hasPathAndMethodDuplicates(rules []gatewayv1beta1.Rule) bool { + duplicates := map[string]bool{} + + if len(rules) > 1 { + for _, rule := range rules { + if len(rule.Methods) > 0 { + for _, method := range rule.Methods { + tmp := fmt.Sprintf("%s:%s", rule.Path, method) + if duplicates[tmp] { + return true + } + duplicates[tmp] = true + } + } else { + if duplicates[rule.Path] { + return true + } + duplicates[rule.Path] = true + } + } + } + + return false +} + +func getOwnerLabels(api *gatewayv1beta1.APIRule) map[string]string { + OwnerLabelv1beta1 := fmt.Sprintf("%s.%s", "apirule", gatewayv1beta1.GroupVersion.String()) + labels := make(map[string]string) + labels[OwnerLabelv1beta1] = fmt.Sprintf("%s.%s", api.ObjectMeta.Name, api.ObjectMeta.Namespace) + return labels +} diff --git a/internal/validation/v2alpha1/v2alpha1.go b/internal/validation/v2alpha1/v2alpha1.go new file mode 100644 index 000000000..e92afdc1f --- /dev/null +++ b/internal/validation/v2alpha1/v2alpha1.go @@ -0,0 +1,58 @@ +package v2alpha1 + +import ( + "context" + gatewayv2alpha1 "github.com/kyma-project/api-gateway/apis/gateway/v2alpha1" + "github.com/kyma-project/api-gateway/internal/validation" + networkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type APIRuleValidator struct { + api *gatewayv2alpha1.APIRule + + // TODO: I don't know if those validators are enough, for now I added some boilerplate code + InjectionValidator *validation.InjectionValidator + RulesValidator rulesValidator + JwtValidator jwtValidator + + DefaultDomainName string +} + +type jwtValidator interface { + Validate(attributePath string, handler *gatewayv2alpha1.JwtConfig) []validation.Failure +} + +type jwtValidatorImpl struct{} + +func (j *jwtValidatorImpl) Validate(attributePath string, jwtConfig *gatewayv2alpha1.JwtConfig) []validation.Failure { + //TODO implement me + panic("implement me") +} + +type rulesValidator interface { + Validate(attributePath string, rules []*gatewayv2alpha1.Rule) []validation.Failure +} + +type rulesValidatorImpl struct{} + +func (r rulesValidatorImpl) Validate(attributePath string, rules []*gatewayv2alpha1.Rule) []validation.Failure { + //TODO implement me + panic("implement me") +} + +func NewAPIRuleValidator(ctx context.Context, client client.Client, api *gatewayv2alpha1.APIRule, defaultDomainName string) *APIRuleValidator { + return &APIRuleValidator{ + api: api, + InjectionValidator: validation.NewInjectionValidator(ctx, client), + RulesValidator: rulesValidatorImpl{}, + JwtValidator: &jwtValidatorImpl{}, + DefaultDomainName: defaultDomainName, + } +} + +// TODO: Actually Validate +func (*APIRuleValidator) Validate(ctx context.Context, client client.Client, vsList networkingv1beta1.VirtualServiceList) []validation.Failure { + //TODO implement me + panic("implement me") +} diff --git a/internal/validation/validate.go b/internal/validation/validate.go index fa004fa30..c219783c5 100644 --- a/internal/validation/validate.go +++ b/internal/validation/validate.go @@ -3,55 +3,15 @@ package validation import ( "context" "fmt" - "github.com/go-logr/logr" - gatewayv1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1" - "github.com/kyma-project/api-gateway/internal/processing/default_domain" - "google.golang.org/appengine/log" - "strings" - "github.com/kyma-project/api-gateway/internal/helpers" + "golang.org/x/exp/slices" networkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" - apiv1beta1 "istio.io/api/type/v1beta1" - "k8s.io/utils/strings/slices" "sigs.k8s.io/controller-runtime/pkg/client" ) -// Validators for AccessStrategies -var vldNoConfig = &noConfigAccStrValidator{} -var vldDummy = &dummyHandlerValidator{} - -type handlerValidator interface { - Validate(attrPath string, Handler *gatewayv1beta1.Handler) []Failure -} - -type accessStrategyValidator interface { - Validate(attrPath string, accessStrategies []*gatewayv1beta1.Authenticator) []Failure -} - -type mutatorValidator interface { - Validate(attrPath string, rule gatewayv1beta1.Rule) []Failure -} - -type injectionValidator interface { - Validate(attrPath string, service *apiv1beta1.WorkloadSelector, namespace string) ([]Failure, error) -} - -type rulesValidator interface { - Validate(attrPath string, rules []gatewayv1beta1.Rule) []Failure -} - -// APIRuleValidator is used to validate github.com/kyma-project/api-gateway/api/v1beta1/APIRule instances -type APIRuleValidator struct { - HandlerValidator handlerValidator - AccessStrategiesValidator accessStrategyValidator - MutatorsValidator mutatorValidator - InjectionValidator injectionValidator - RulesValidator rulesValidator - ServiceBlockList map[string][]string - DomainAllowList []string - HostBlockList []string - DefaultDomainName string +type ApiRuleValidator interface { + Validate(ctx context.Context, client client.Client, vsList networkingv1beta1.VirtualServiceList) []Failure } // Failure carries validation failures for a single attribute of an object. @@ -60,21 +20,7 @@ type Failure struct { Message string } -// Validate performs APIRule validation -func (v *APIRuleValidator) Validate(ctx context.Context, client client.Client, api *gatewayv1beta1.APIRule, vsList networkingv1beta1.VirtualServiceList) []Failure { - var failures []Failure - - //Validate service on path level if it is created - if api.Spec.Service != nil { - failures = append(failures, v.validateService(".spec.service", api)...) - } - failures = append(failures, v.validateHost(".spec.host", vsList, api)...) - failures = append(failures, v.validateRules(ctx, client, ".spec.rules", api.Spec.Service == nil, api)...) - - return failures -} - -func (v *APIRuleValidator) ValidateConfig(config *helpers.Config) []Failure { +func ValidateConfig(config *helpers.Config) []Failure { var problems []Failure if config == nil { @@ -91,239 +37,3 @@ func (v *APIRuleValidator) ValidateConfig(config *helpers.Config) []Failure { return problems } - -func (v *APIRuleValidator) validateHost(attributePath string, vsList networkingv1beta1.VirtualServiceList, api *gatewayv1beta1.APIRule) []Failure { - var problems []Failure - if api.Spec.Host == nil { - problems = append(problems, Failure{ - AttributePath: attributePath, - Message: "Host was nil", - }) - return problems - } - - host := *api.Spec.Host - if !default_domain.HostIncludesDomain(*api.Spec.Host) { - if v.DefaultDomainName == "" { - problems = append(problems, Failure{ - AttributePath: attributePath, - Message: "Host does not contain a domain name and no default domain name is configured", - }) - } - host = default_domain.GetHostWithDefaultDomain(host, v.DefaultDomainName) - } else if len(v.DomainAllowList) > 0 { - // Do the allowList check only if the list is actually provided AND the default domain name is not used. - domainFound := false - for _, domain := range v.DomainAllowList { - // service host containing duplicated allowlisted domain should be rejected. - // for example `my-lambda.kyma.local.kyma.local` - // service host containing allowlisted domain but only as a part of bigger domain should also be rejected - // for example `my-lambda.kyma.local.com` when only `kyma.local` is allowlisted - if count := strings.Count(host, domain); count == 1 && strings.HasSuffix(host, domain) { - domainFound = true - } - } - if !domainFound { - problems = append(problems, Failure{ - AttributePath: attributePath, - Message: "Host is not allowlisted", - }) - } - } - - for _, blockedHost := range v.HostBlockList { - host := *api.Spec.Host - if blockedHost == host { - subdomain := strings.Split(host, ".")[0] - problems = append(problems, Failure{ - AttributePath: attributePath, - Message: fmt.Sprintf("The subdomain %s is blocklisted for %s domain", subdomain, v.DefaultDomainName), - }) - } - } - - for _, vs := range vsList.Items { - if occupiesHost(vs, host) && !ownedBy(vs, api) { - problems = append(problems, Failure{ - AttributePath: attributePath, - Message: "This host is occupied by another Virtual Service", - }) - } - } - - return problems -} - -func (v *APIRuleValidator) validateService(attributePath string, api *gatewayv1beta1.APIRule) []Failure { - var problems []Failure - - for namespace, services := range v.ServiceBlockList { - for _, svc := range services { - serviceNamespace := helpers.FindServiceNamespace(api, nil) - if api != nil && svc == *api.Spec.Service.Name && namespace == serviceNamespace { - problems = append(problems, Failure{ - AttributePath: attributePath + ".name", - Message: fmt.Sprintf("Service %s in namespace %s is blocklisted", svc, namespace), - }) - } - } - } - - return problems -} - -// Validates whether all rules are defined correctly -// Checks whether all rules have service defined for them if checkForService is true -func (v *APIRuleValidator) validateRules(ctx context.Context, client client.Client, attributePath string, checkForService bool, api *gatewayv1beta1.APIRule) []Failure { - var problems []Failure - - rules := api.Spec.Rules - if len(rules) == 0 { - problems = append(problems, Failure{AttributePath: attributePath, Message: "No rules defined"}) - return problems - } - - if hasPathAndMethodDuplicates(rules) { - problems = append(problems, Failure{AttributePath: attributePath, Message: "multiple rules defined for the same path and method"}) - } - - for i, r := range rules { - attributePathWithRuleIndex := fmt.Sprintf("%s[%d]", attributePath, i) - if checkForService && r.Service == nil { - problems = append(problems, Failure{AttributePath: attributePathWithRuleIndex + ".service", Message: "No service defined with no main service on spec level"}) - } - if r.Service != nil { - labelSelector, err := helpers.GetLabelSelectorFromService(ctx, client, r.Service, api, &r) - if err != nil { - l, errorCtx := logr.FromContext(ctx) - if errorCtx != nil { - log.Errorf(ctx, "No logger in context: %s", errorCtx) - } else { - l.Info("Couldn't get label selectors for service", "error", err) - } - } - problems = append(problems, v.validateAccessStrategies(attributePathWithRuleIndex+".accessStrategies", r.AccessStrategies, labelSelector, helpers.FindServiceNamespace(api, &r))...) - for namespace, services := range v.ServiceBlockList { - for _, svc := range services { - serviceNamespace := helpers.FindServiceNamespace(api, &r) - if svc == *r.Service.Name && namespace == serviceNamespace { - problems = append(problems, Failure{ - AttributePath: attributePathWithRuleIndex + ".service.name", - Message: fmt.Sprintf("Service %s in namespace %s is blocklisted", svc, namespace), - }) - } - } - } - } else if api.Spec.Service != nil { - labelSelector, err := helpers.GetLabelSelectorFromService(ctx, client, api.Spec.Service, api, nil) - if err != nil { - l, errorCtx := logr.FromContext(ctx) - if errorCtx != nil { - log.Errorf(ctx, "No logger in context: %s", errorCtx) - } else { - l.Info("Couldn't get label selectors for service", "error", err) - } - } - problems = append(problems, v.validateAccessStrategies(attributePathWithRuleIndex+".accessStrategies", r.AccessStrategies, labelSelector, helpers.FindServiceNamespace(api, &r))...) - } - - if v.MutatorsValidator != nil { - mutatorFailures := v.MutatorsValidator.Validate(attributePathWithRuleIndex, r) - problems = append(problems, mutatorFailures...) - } - - } - - if v.RulesValidator != nil { - rulesFailures := v.RulesValidator.Validate(".spec.rules", rules) - problems = append(problems, rulesFailures...) - } - - return problems -} - -func (v *APIRuleValidator) validateAccessStrategies(attributePath string, accessStrategies []*gatewayv1beta1.Authenticator, selector *apiv1beta1.WorkloadSelector, namespace string) []Failure { - var problems []Failure - - if len(accessStrategies) == 0 { - problems = append(problems, Failure{AttributePath: attributePath, Message: "No accessStrategies defined"}) - return problems - } - - problems = append(problems, v.AccessStrategiesValidator.Validate(attributePath, accessStrategies)...) - problems = append(problems, CheckForSecureAndUnsecureAccessStrategies(accessStrategies, attributePath)...) - - for i, r := range accessStrategies { - strategyAttrPath := attributePath + fmt.Sprintf("[%d]", i) - problems = append(problems, v.validateAccessStrategy(strategyAttrPath, r, selector, namespace)...) - } - - return problems -} - -func (v *APIRuleValidator) validateAccessStrategy(attributePath string, accessStrategy *gatewayv1beta1.Authenticator, selector *apiv1beta1.WorkloadSelector, namespace string) []Failure { - var problems []Failure - var vld handlerValidator - - switch accessStrategy.Handler.Name { - case gatewayv1beta1.AccessStrategyAllow: - vld = vldNoConfig - case gatewayv1beta1.AccessStrategyNoAuth: - vld = vldNoConfig - case gatewayv1beta1.AccessStrategyNoop: - vld = vldNoConfig - case gatewayv1beta1.AccessStrategyUnauthorized: - vld = vldNoConfig - case gatewayv1beta1.AccessStrategyAnonymous: - vld = vldNoConfig - case gatewayv1beta1.AccessStrategyCookieSession: - vld = vldNoConfig - case gatewayv1beta1.AccessStrategyOauth2ClientCredentials: - vld = vldDummy - case gatewayv1beta1.AccessStrategyOauth2Introspection: - vld = vldDummy - case gatewayv1beta1.AccessStrategyJwt: - vld = v.HandlerValidator - if v.InjectionValidator != nil { - injectionProblems, err := v.InjectionValidator.Validate(attributePath+".injection", selector, namespace) - if err != nil { - problems = append(problems, Failure{AttributePath: attributePath + ".handler", Message: fmt.Sprintf("Could not find pod for selected service, err: %s", err)}) - } else { - problems = append(problems, injectionProblems...) - } - } - default: - return []Failure{{AttributePath: attributePath + ".handler", Message: fmt.Sprintf("Unsupported accessStrategy: %s", accessStrategy.Handler.Name)}} - } - - return append(problems, vld.Validate(attributePath, accessStrategy.Handler)...) -} - -func occupiesHost(vs *networkingv1beta1.VirtualService, host string) bool { - for _, h := range vs.Spec.Hosts { - if h == host { - return true - } - } - return false -} - -func getOwnerLabels(api *gatewayv1beta1.APIRule) map[string]string { - OwnerLabelv1beta1 := fmt.Sprintf("%s.%s", "apirule", gatewayv1beta1.GroupVersion.String()) - labels := make(map[string]string) - labels[OwnerLabelv1beta1] = fmt.Sprintf("%s.%s", api.ObjectMeta.Name, api.ObjectMeta.Namespace) - return labels -} - -func ownedBy(vs *networkingv1beta1.VirtualService, api *gatewayv1beta1.APIRule) bool { - ownerLabels := getOwnerLabels(api) - vsLabels := vs.GetLabels() - - for key, label := range ownerLabels { - val, ok := vsLabels[key] - if ok { - return val == label - } - } - return false -}