diff --git a/pkg/apis/compliance/v1alpha1/tailoredprofile_types.go b/pkg/apis/compliance/v1alpha1/tailoredprofile_types.go index 61bd10435..026a46bdf 100644 --- a/pkg/apis/compliance/v1alpha1/tailoredprofile_types.go +++ b/pkg/apis/compliance/v1alpha1/tailoredprofile_types.go @@ -12,6 +12,12 @@ const OutdatedReferenceValidationDisable = "compliance.openshift.io/outdated-ref // RuleLastCheckTypeChangedAnnotationKey is the annotation key used to indicate that the rule check type has changed, store its previous check type const RuleLastCheckTypeChangedAnnotationKey = "compliance.openshift.io/rule-last-check-type" +// VariableDeprecatedAnnotationKey is the annotation key used to indicate that the variable is deprecated +const VariableDeprecatedAnnotationKey = "compliance.openshift.io/variable-deprecated" + +// RulesDeprecatedAnnotationKey is the annotation key used to indicate that the rule is deprecated +const RulesDeprecatedAnnotationKey = "compliance.openshift.io/rule-deprecated" + // RuleReferenceSpec specifies a rule to be selected/deselected, as well as the reason why type RuleReferenceSpec struct { // Name of the rule that's being referenced diff --git a/pkg/controller/tailoredprofile/tailoredprofile_controller.go b/pkg/controller/tailoredprofile/tailoredprofile_controller.go index 0621b19a2..e6a2f3e89 100644 --- a/pkg/controller/tailoredprofile/tailoredprofile_controller.go +++ b/pkg/controller/tailoredprofile/tailoredprofile_controller.go @@ -48,7 +48,7 @@ func Add(mgr manager.Manager, met *metrics.Metrics, _ utils.CtlplaneSchedulingIn // newReconciler returns a new reconcile.Reconciler func newReconciler(mgr manager.Manager, met *metrics.Metrics) reconcile.Reconciler { - return &ReconcileTailoredProfile{Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Metrics: met} + return &ReconcileTailoredProfile{Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Metrics: met, Recorder: common.NewSafeRecorder("tailoredprofile-controller", mgr)} } // add adds a new Controller to mgr with r as the reconcile.Reconciler @@ -63,13 +63,22 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // blank assignment to verify that ReconcileTailoredProfile implements reconcile.Reconciler var _ reconcile.Reconciler = &ReconcileTailoredProfile{} +func (r *ReconcileTailoredProfile) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) { + if r.Recorder == nil { + return + } + + r.Recorder.Eventf(object, eventtype, reason, messageFmt, args...) +} + // ReconcileTailoredProfile reconciles a TailoredProfile object type ReconcileTailoredProfile struct { // This Client, initialized using mgr.Client() above, is a split Client // that reads objects from the cache and writes to the apiserver - Client client.Client - Scheme *runtime.Scheme - Metrics *metrics.Metrics + Client client.Client + Scheme *runtime.Scheme + Metrics *metrics.Metrics + Recorder *common.SafeRecorder } // Reconcile reads that state of the cluster for a TailoredProfile object and makes changes based on the state read @@ -109,14 +118,52 @@ func (r *ReconcileTailoredProfile) Reconcile(ctx context.Context, request reconc return reconcile.Result{}, pbgetErr } - // Make TailoredProfile be owned by the Profile it extends. This way - // we can ensure garbage collection happens. - // This update will trigger a requeue with the new object. + needsAnnotation := false + + if instance.GetAnnotations() == nil { + needsAnnotation = true + } else { + if _, ok := instance.GetAnnotations()[cmpv1alpha1.ProductTypeAnnotation]; !ok { + needsAnnotation = true + } + } + + if needsAnnotation { + tpCopy := instance.DeepCopy() + anns := tpCopy.GetAnnotations() + if anns == nil { + anns = make(map[string]string) + } + + scanType := utils.GetScanType(p.GetAnnotations()) + anns[cmpv1alpha1.ProductTypeAnnotation] = string(scanType) + tpCopy.SetAnnotations(anns) + // Make TailoredProfile be owned by the Profile it extends. This way + // we can ensure garbage collection happens. + // This update will trigger a requeue with the new object. + if needsControllerRef(tpCopy) { + return r.setOwnership(tpCopy, p) + } else { + r.Client.Update(context.TODO(), tpCopy) + return reconcile.Result{}, nil + } + } + if needsControllerRef(instance) { tpCopy := instance.DeepCopy() return r.setOwnership(tpCopy, p) } + } else { + if !isValidationRequired(instance) { + // check if the TailoredProfile is empty without any extends + // if it is empty, we should not update the tp, and set the state of tp to Error + err = r.handleTailoredProfileStatusError(instance, fmt.Errorf("Custom no extends TailoredProfile does not have any rules enabled")) + if err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{}, nil + } var pbgetErr error pb, pbgetErr = r.getProfileBundleFromRulesOrVars(instance) if pbgetErr != nil && !common.IsRetriable(pbgetErr) { @@ -152,6 +199,28 @@ func (r *ReconcileTailoredProfile) Reconcile(ctx context.Context, request reconc } } + ann := instance.GetAnnotations() + if _, ok := ann[cmpv1alpha1.OutdatedReferenceValidationDisable]; ok { + reqLogger.Info("Reference validation is disabled, skipping validation") + } else if isValidationRequired(instance) { + // remove any deprecated variables or rules from the tailored profile + doContinue, err := r.handleDeprecation(instance, reqLogger) + if err != nil { + return reconcile.Result{}, err + } + if !doContinue { + return reconcile.Result{}, nil + } + + doContinue, err = r.handleMigration(instance, reqLogger) + if err != nil { + return reconcile.Result{}, err + } + if !doContinue { + return reconcile.Result{}, nil + } + } + rules, ruleErr := r.getRulesFromSelections(instance, pb) if ruleErr != nil && !common.IsRetriable(ruleErr) { // Surface the error. @@ -187,6 +256,280 @@ func (r *ReconcileTailoredProfile) Reconcile(ctx context.Context, request reconc return r.ensureOutputObject(instance, tpcm, reqLogger) } +// handleMigration checks if the TailoredProfile has any deprecated variables or or any updated KubeletConfig rules +// and handle the migration +func (r *ReconcileTailoredProfile) handleMigration( + v1alphaTp *cmpv1alpha1.TailoredProfile, logger logr.Logger) (bool, error) { + + // Get the list of KubeletConfig rules that are migrated with checkType change + migratedRules, err := r.getMigratedRules(v1alphaTp, logger) + if err != nil { + return false, err + } + + if len(migratedRules) == 0 { + return true, nil + } + + profileType := utils.GetScanType(v1alphaTp.GetAnnotations()) + + doContinue := true + v1alphaTpCP := v1alphaTp.DeepCopy() + + // check if there are any disabled rules that are migrated + if len(v1alphaTp.Spec.DisableRules) > 0 { + var newRules []cmpv1alpha1.RuleReferenceSpec + for ri := range v1alphaTp.Spec.DisableRules { + rule := &v1alphaTp.Spec.DisableRules[ri] + if checkType, ok := migratedRules[rule.Name]; ok && checkType != string(profileType) { + // remove the rule from the list of disabled rules + doContinue = false + logger.Info("Removing migrated rule from disableRules", "rule", rule.Name) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileMigratedRule", "Removing migrated rule: %s from disableRules", rule.Name) + continue + } + newRules = append(newRules, *rule) + } + if len(newRules) != len(v1alphaTp.Spec.DisableRules) { + v1alphaTpCP.Spec.DisableRules = newRules + } + } + + // check if there are any enabled rules that are migrated + if len(v1alphaTp.Spec.EnableRules) > 0 { + var newRules []cmpv1alpha1.RuleReferenceSpec + for ri := range v1alphaTp.Spec.EnableRules { + rule := &v1alphaTp.Spec.EnableRules[ri] + if checkType, ok := migratedRules[rule.Name]; ok && checkType != string(profileType) { + doContinue = false + logger.Info("Removing migrated rule from enableRules", "rule", rule.Name) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileMigratedRule", "Removing migrated rule %s from enableRules", rule.Name) + continue + } + newRules = append(newRules, *rule) + } + + if len(newRules) != len(v1alphaTp.Spec.EnableRules) { + v1alphaTpCP.Spec.EnableRules = newRules + } + if v1alphaTp.Spec.Extends == "" && len(newRules) == 0 && len(v1alphaTpCP.Spec.DisableRules) == 0 && !doContinue { + // flip the checkType of the TailoredProfile to the new checkType if the TailoredProfile does not extend any profile + // and it does not have any rules left after removing migrated rules and the before migration TailoredProfile has + // enableRules + if profileType == cmpv1alpha1.ScanTypeNode { + v1alphaTpCP.SetAnnotations(map[string]string{cmpv1alpha1.ProductTypeAnnotation: string(cmpv1alpha1.ScanTypePlatform)}) + } else { + v1alphaTpCP.SetAnnotations(map[string]string{cmpv1alpha1.ProductTypeAnnotation: string(cmpv1alpha1.ScanTypeNode)}) + } + v1alphaTpCP.Spec.EnableRules = v1alphaTp.Spec.EnableRules + logger.Info("Flipping the checkType of the TailoredProfile to the new checkType", "checkType", profileType) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileMigratedCheckType", "Flipping the checkType of the TailoredProfile to the new checkType: %s", profileType) + } + } + + if v1alphaTp.Spec.Extends == "" && len(v1alphaTpCP.Spec.DisableRules) == 0 && len(v1alphaTpCP.Spec.EnableRules) == 0 { + errorMsg := "TailoredProfile does not have any rules left after removing migrated rules and it does not extend any profile" + v1alphaTpCP.Status.State = cmpv1alpha1.TailoredProfileStateError + v1alphaTpCP.Status.ErrorMessage = errorMsg + doContinue = false + logger.Info(errorMsg) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileMigratedRule", errorMsg) + } + + if !doContinue { + err = r.Client.Update(context.TODO(), v1alphaTpCP) + logger.Info("Updating TailoredProfile with migrated rules removed") + if err != nil { + return false, err + } + } + return doContinue, nil +} + +func isValidationRequired(tp *cmpv1alpha1.TailoredProfile) bool { + if tp.Spec.Extends != "" { + if tp.Spec.DisableRules != nil || tp.Spec.EnableRules != nil || tp.Spec.ManualRules != nil || tp.Spec.SetValues != nil { + return true + } + } else { + if tp.Spec.EnableRules != nil || tp.Spec.ManualRules != nil { + return true + } + } + return false +} + +func (r *ReconcileTailoredProfile) handleDeprecation( + v1alphaTp *cmpv1alpha1.TailoredProfile, logger logr.Logger) (bool, error) { + + deprecatedRules, err := r.getDeprecatedRules(v1alphaTp, logger) + if err != nil { + return false, err + } + + deprecatedVariables, err := r.getDeprecatedVariables(v1alphaTp, logger) + if err != nil { + return false, err + } + + doContinue := true + v1alphaTpCP := v1alphaTp.DeepCopy() + + // remove any deprecated variables from the tailored profile + if len(v1alphaTp.Spec.SetValues) > 0 && len(deprecatedVariables) > 0 { + var newVariables []cmpv1alpha1.VariableValueSpec + for vi := range v1alphaTp.Spec.SetValues { + variables := &v1alphaTp.Spec.SetValues[vi] + if _, ok := deprecatedVariables[variables.Name]; ok { + doContinue = false + logger.Info("Removing deprecated variable", "variable", variables.Name) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileDeprecatedSetting", "Removing deprecated variable: %s", variables.Name) + continue + } else { + newVariables = append(newVariables, *variables) + } + } + if len(newVariables) != len(v1alphaTp.Spec.SetValues) { + v1alphaTpCP.Spec.SetValues = newVariables + } + } + + // remove any deprecated rules from the tailored profile + if len(v1alphaTp.Spec.EnableRules) > 0 && len(deprecatedRules) > 0 { + var newRules []cmpv1alpha1.RuleReferenceSpec + for ri := range v1alphaTp.Spec.EnableRules { + rule := &v1alphaTp.Spec.EnableRules[ri] + if _, ok := deprecatedRules[rule.Name]; ok { + doContinue = false + logger.Info("Removing deprecated rule from enableRules", "rule", rule.Name) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileDeprecatedSetting", "Removing deprecated rule: %s", rule.Name) + continue + } else { + newRules = append(newRules, *rule) + } + } + if len(newRules) != len(v1alphaTp.Spec.EnableRules) { + v1alphaTpCP.Spec.EnableRules = newRules + } + } + + if len(v1alphaTp.Spec.DisableRules) > 0 && len(deprecatedRules) > 0 { + var newRules []cmpv1alpha1.RuleReferenceSpec + for ri := range v1alphaTp.Spec.DisableRules { + rule := &v1alphaTp.Spec.DisableRules[ri] + if _, ok := deprecatedRules[rule.Name]; ok { + doContinue = false + logger.Info("Removing deprecated rule from disableRules", "rule", rule.Name) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileDeprecatedSetting", "Removing deprecated rule: %s", rule.Name) + continue + } else { + newRules = append(newRules, *rule) + } + } + if len(newRules) != len(v1alphaTp.Spec.DisableRules) { + v1alphaTpCP.Spec.DisableRules = newRules + } + } + + // if tp does not have any rules, variables left, and it does not extend any profile + // we should not update the tp, and set the state of tp to Error + if len(v1alphaTpCP.Spec.EnableRules) == 0 && len(v1alphaTpCP.Spec.DisableRules) == 0 && len(v1alphaTpCP.Spec.ManualRules) == 0 && v1alphaTpCP.Spec.Extends == "" { + errorMsg := "TailoredProfile does not have any rules left after removing deprecated rules and variables" + v1alphaTpCP.Status.State = cmpv1alpha1.TailoredProfileStateError + v1alphaTpCP.Status.ErrorMessage = errorMsg + doContinue = false + logger.Info(errorMsg) + r.Eventf(v1alphaTp, corev1.EventTypeWarning, "TailoredProfileDeprecatedSetting", errorMsg) + } + + if !doContinue { + err = r.Client.Update(context.TODO(), v1alphaTpCP) + logger.Info("Updating TailoredProfile with deprecated rules and variables removed") + if err != nil { + return false, err + } + } + return doContinue, nil +} + +// getMigratedRules get list of rules and check if it has RuleLastCheckTypeChangedAnnotationKey annotation +// if it does, add it to the map with the current check type +func (r *ReconcileTailoredProfile) getMigratedRules(tp *cmpv1alpha1.TailoredProfile, logger logr.Logger) (map[string]string, error) { + // get all the rules in the namespace + ruleList := &cmpv1alpha1.RuleList{} + err := r.Client.List(context.TODO(), ruleList, &client.ListOptions{ + Namespace: tp.GetNamespace(), + }) + if err != nil { + return nil, err + } + + // get all the rules that are migrated + migratedRules := make(map[string]string) + for ri := range ruleList.Items { + rule := &ruleList.Items[ri] + if rule.Annotations != nil { + if _, ok := rule.Annotations[cmpv1alpha1.RuleLastCheckTypeChangedAnnotationKey]; ok { + if rule.CheckType == cmpv1alpha1.CheckTypeNone { + logger.Info("Rule has been changed to manual check", "rule", rule.GetName()) + r.Eventf(tp, corev1.EventTypeWarning, "TailoredProfileMigratedRule", "Rule has been changed to manual check: %s", rule.GetName()) + continue + } + migratedRules[rule.GetName()] = rule.CheckType + } + } + } + return migratedRules, nil +} + +// getDeprecatedVariables gets the list of deprecated variables +func (r *ReconcileTailoredProfile) getDeprecatedVariables(tp *cmpv1alpha1.TailoredProfile, logger logr.Logger) (map[string]string, error) { + // get all the variables in the namespace + variableList := &cmpv1alpha1.VariableList{} + err := r.Client.List(context.TODO(), variableList, &client.ListOptions{ + Namespace: tp.GetNamespace(), + }) + if err != nil { + return nil, err + } + + // get all the variables that are deprecated + deprecatedVariables := make(map[string]string) + for vi := range variableList.Items { + variable := &variableList.Items[vi] + if variable.Annotations != nil { + if _, ok := variable.Annotations[cmpv1alpha1.VariableDeprecatedAnnotationKey]; ok { + deprecatedVariables[variable.GetName()] = "" + } + } + } + return deprecatedVariables, nil +} + +// getDeprecatedRules gets the list of deprecated rules +func (r *ReconcileTailoredProfile) getDeprecatedRules(tp *cmpv1alpha1.TailoredProfile, logger logr.Logger) (map[string]string, error) { + // get all the rules in the namespace + ruleList := &cmpv1alpha1.RuleList{} + err := r.Client.List(context.TODO(), ruleList, &client.ListOptions{ + Namespace: tp.GetNamespace(), + }) + if err != nil { + return nil, err + } + + // get all the rules that are deprecated + deprecatedRules := make(map[string]string) + for ri := range ruleList.Items { + rule := &ruleList.Items[ri] + if rule.Annotations != nil { + if _, ok := rule.Annotations[cmpv1alpha1.RulesDeprecatedAnnotationKey]; ok { + deprecatedRules[rule.GetName()] = "" + } + } + } + return deprecatedRules, nil +} + // getProfileInfoFromExtends gets the Profile and ProfileBundle where the rules come from // out of the profile that's being extended func (r *ReconcileTailoredProfile) getProfileInfoFromExtends(tp *cmpv1alpha1.TailoredProfile) (*cmpv1alpha1.Profile, *cmpv1alpha1.ProfileBundle, error) { diff --git a/pkg/utils/nodeutils.go b/pkg/utils/nodeutils.go index 246912c14..de03d1e54 100644 --- a/pkg/utils/nodeutils.go +++ b/pkg/utils/nodeutils.go @@ -25,6 +25,7 @@ import ( "strconv" "strings" + compliancev1alpha1 "github.com/ComplianceAsCode/compliance-operator/pkg/apis/compliance/v1alpha1" "github.com/PaesslerAG/jsonpath" mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" "k8s.io/apimachinery/pkg/types" @@ -143,6 +144,23 @@ func IsMcfgPoolUsingKC(pool *mcfgv1.MachineConfigPool) (bool, string, error) { return true, currentKCMC, nil } +func GetScanType(annotations map[string]string) compliancev1alpha1.ComplianceScanType { + // The default type is platform + platformType, ok := annotations[compliancev1alpha1.ProductTypeAnnotation] + if !ok { + return compliancev1alpha1.ScanTypePlatform + } + + switch strings.ToLower(platformType) { + case strings.ToLower(string(compliancev1alpha1.ScanTypeNode)): + return compliancev1alpha1.ScanTypeNode + default: + break + } + + return compliancev1alpha1.ScanTypePlatform +} + func AreKubeletConfigsRendered(pool *mcfgv1.MachineConfigPool, client runtimeclient.Client) (bool, error, string) { // find out if pool is using a custom kubelet config isUsingKC, currentKCMCName, err := IsMcfgPoolUsingKC(pool) diff --git a/tests/e2e/framework/common.go b/tests/e2e/framework/common.go index be69d19ad..1514d26f7 100644 --- a/tests/e2e/framework/common.go +++ b/tests/e2e/framework/common.go @@ -136,6 +136,11 @@ func (f *Framework) createFromYAMLFile(p *string) error { if err != nil { return err } + return f.createFromYAMLString(string(c)) +} + +func (f *Framework) createFromYAMLString(y string) error { + c := []byte(y) documents, err := f.readYAML(c) if err != nil { return err @@ -787,6 +792,64 @@ func (f *Framework) WaitForScanStatus(namespace, name string, targetStatus compv return nil } +func (f *Framework) EnableRuleExistInTailoredProfile(namespace, name, ruleName string) (bool, error) { + tp := &compv1alpha1.TailoredProfile{} + defer f.logContainerOutput(namespace, name) + if tp.Status.State == compv1alpha1.TailoredProfileStateReady || tp.Status.State == compv1alpha1.TailoredProfileStateError { + // check if rule exist in tailoredprofile enableRules + hasRule := false + for _, r := range tp.Spec.EnableRules { + if r.Name == ruleName { + hasRule = true + break + } + } + if hasRule { + log.Printf("rule %s exist in tailoredprofile enableRules\n", ruleName) + return true, nil + } else { + log.Printf("rule %s does not exist in tailoredprofile enableRules\n", ruleName) + return false, nil + } + } + + return false, fmt.Errorf("tailoredprofile %s is not ready", name) +} + +func (f *Framework) WaitForTailoredProfileStatus(namespace, name string, targetStatus compv1alpha1.TailoredProfileState) error { + tp := &compv1alpha1.TailoredProfile{} + var lastErr error + defer f.logContainerOutput(namespace, name) + // retry and ignore errors until timeout + timeoutErr := wait.Poll(RetryInterval, Timeout, func() (bool, error) { + lastErr = f.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, tp) + if lastErr != nil { + if apierrors.IsNotFound(lastErr) { + log.Printf("Waiting for availability of %s tailoredprofile\n", name) + return false, nil + } + log.Printf("Retrying. Got error: %v\n", lastErr) + return false, nil + } + + if tp.Status.State == targetStatus { + return true, nil + } + log.Printf("Waiting for run of %s tailoredprofile (%s)\n", name, tp.Status.State) + return false, nil + }) + + if timeoutErr != nil { + return fmt.Errorf("failed waiting for tailoredprofile %s due to timeout: %s", name, timeoutErr) + } + if lastErr != nil { + return fmt.Errorf("failed waiting for tailoredprofile %s: %s", name, lastErr) + } + + log.Printf("TailoredProfile ready (%s)\n", tp.Status.State) + return nil +} + // waitForScanStatus will poll until the compliancescan that we're lookingfor reaches a certain status, or until // a timeout is reached. func (f *Framework) WaitForSuiteScansStatus(namespace, name string, targetStatus compv1alpha1.ComplianceScanStatusPhase, targetComplianceStatus compv1alpha1.ComplianceScanStatusResult) error { diff --git a/tests/e2e/parallel/main_test.go b/tests/e2e/parallel/main_test.go index b53a3bea7..73a8822db 100644 --- a/tests/e2e/parallel/main_test.go +++ b/tests/e2e/parallel/main_test.go @@ -2210,12 +2210,10 @@ func TestScanSettingBinding(t *testing.T) { } -func TestScanSettingBindingTailoringAndNonDefaultRole(t *testing.T) { +func TestScanSettingBindingTailoringOneEnablingRulePass(t *testing.T) { t.Parallel() f := framework.Global - tpName := "non-default-role-tp" - scanSettingBindingName := "non-default-role-ssb" - + tpName := "migrated-tp-only-one-enabling-rule" tp := &compv1alpha1.TailoredProfile{ ObjectMeta: metav1.ObjectMeta{ Name: tpName, @@ -2224,7 +2222,6 @@ func TestScanSettingBindingTailoringAndNonDefaultRole(t *testing.T) { Spec: compv1alpha1.TailoredProfileSpec{ Title: "TestCisForE2EPool", Description: "TestCisForE2EPool", - Extends: "ocp4-cis", SetValues: []compv1alpha1.VariableValueSpec{ { Name: "ocp4-var-role-master", @@ -2237,6 +2234,12 @@ func TestScanSettingBindingTailoringAndNonDefaultRole(t *testing.T) { Value: "e2e", }, }, + EnableRules: []compv1alpha1.RuleReferenceSpec{ + { + Name: "ocp4-kubelet-anonymous-auth", + Rationale: "this rule should not be removed from the profile, we will flip the checkType of the tailoredProfile", + }, + }, }, } @@ -2245,37 +2248,87 @@ func TestScanSettingBindingTailoringAndNonDefaultRole(t *testing.T) { t.Fatal(createTPErr) } defer f.Client.Delete(context.TODO(), tp) + // check the status of the TP to make sure it has errors + err := f.WaitForTailoredProfileStatus(f.OperatorNamespace, tpName, compv1alpha1.TailoredProfileStateReady) + if err != nil { + t.Fatal(err) + } - scanSettingBinding := compv1alpha1.ScanSettingBinding{ + hasRule, err := f.EnableRuleExistInTailoredProfile(f.OperatorNamespace, tpName, "ocp4-kubelet-anonymous-auth") + if err != nil { + t.Fatal(err) + } + if !hasRule { + t.Fatal("Expected the tailored profile to have the rule") + } + + defer f.Client.Delete(context.TODO(), tp) +} + +func TestScanSettingBindingTailoringManyEnablingRulePass(t *testing.T) { + t.Parallel() + f := framework.Global + tpName := "many-migrated-mix-tp" + tp := &compv1alpha1.TailoredProfile{ ObjectMeta: metav1.ObjectMeta{ - Name: scanSettingBindingName, + Name: tpName, Namespace: f.OperatorNamespace, }, - Profiles: []compv1alpha1.NamedObjectReference{ - { - Name: tpName, - Kind: "TailoredProfile", - APIGroup: "compliance.openshift.io/v1alpha1", + Spec: compv1alpha1.TailoredProfileSpec{ + Title: "TestForManyRules", + Description: "TestForManyRules", + EnableRules: []compv1alpha1.RuleReferenceSpec{ + { + Name: "ocp4-kubelet-anonymous-auth", + Rationale: "this rule should be removed from the profile", + }, + { + Name: "ocp4-api-server-insecure-port", + Rationale: "this rule should not be removed from the profile", + }, + { + Name: "ocp4-kubelet-enable-streaming-connections", + Rationale: "this rule should be removed from the profile", + }, }, }, - SettingsRef: &compv1alpha1.NamedObjectReference{ - Name: "e2e-default", - Kind: "ScanSetting", - APIGroup: "compliance.openshift.io/v1alpha1", - }, } - err := f.Client.Create(context.TODO(), &scanSettingBinding, nil) + createTPErr := f.Client.Create(context.TODO(), tp, nil) + if createTPErr != nil { + t.Fatal(createTPErr) + } + defer f.Client.Delete(context.TODO(), tp) + // check the status of the TP to make sure it has errors + err := f.WaitForTailoredProfileStatus(f.OperatorNamespace, tpName, compv1alpha1.TailoredProfileStateReady) if err != nil { t.Fatal(err) } - defer f.Client.Delete(context.TODO(), &scanSettingBinding) - // it's enough to check that the scan finishes - err = f.WaitForSuiteScansStatus(f.OperatorNamespace, scanSettingBindingName, compv1alpha1.PhaseDone, compv1alpha1.ResultNonCompliant) + hasRule, err := f.EnableRuleExistInTailoredProfile(f.OperatorNamespace, tpName, "ocp4-kubelet-anonymous-auth") + if err != nil { + t.Fatal(err) + } + if hasRule { + t.Fatal("Expected the tailored profile to not have the rule") + } + + hasRule, err = f.EnableRuleExistInTailoredProfile(f.OperatorNamespace, tpName, "ocp4-kubelet-enable-streaming-connections") if err != nil { t.Fatal(err) } + if hasRule { + t.Fatal("Expected the tailored profile to not have the rule") + } + + hasRule, err = f.EnableRuleExistInTailoredProfile(f.OperatorNamespace, tpName, "ocp4-api-server-insecure-port") + if err != nil { + t.Fatal(err) + } + if !hasRule { + t.Fatal("Expected the tailored profile to have the rule") + } + defer f.Client.Delete(context.TODO(), tp) } func TestScanSettingBindingUsesDefaultScanSetting(t *testing.T) { @@ -2503,6 +2556,9 @@ func TestManualRulesTailoredProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: suiteName, Namespace: f.OperatorNamespace, + Labels: map[string]string{ + compv1alpha1.OutdatedReferenceValidationDisable: "true", + }, }, Spec: compv1alpha1.TailoredProfileSpec{ Title: "manual-rules-test", @@ -2624,6 +2680,9 @@ func TestCheckDefaultKubeletConfig(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: suiteName, Namespace: f.OperatorNamespace, + Labels: map[string]string{ + compv1alpha1.OutdatedReferenceValidationDisable: "true", + }, }, Spec: compv1alpha1.TailoredProfileSpec{ Title: "kubelet-default-test",