diff --git a/config/crd/bases/compliance.openshift.io_compliancesuites.yaml b/config/crd/bases/compliance.openshift.io_compliancesuites.yaml index aa8f09567..4be6edc5e 100644 --- a/config/crd/bases/compliance.openshift.io_compliancesuites.yaml +++ b/config/crd/bases/compliance.openshift.io_compliancesuites.yaml @@ -55,6 +55,11 @@ spec: automatically. This is done by deleting the "outdated" object from the remediation. type: boolean + disableRemediations: + default: false + description: Defines whether remediations should be allowed to be + applied + type: boolean scans: description: Contains a list of the scans to execute on the cluster items: diff --git a/config/crd/bases/compliance.openshift.io_scansettings.yaml b/config/crd/bases/compliance.openshift.io_scansettings.yaml index b997c7a67..b8c7296b1 100644 --- a/config/crd/bases/compliance.openshift.io_scansettings.yaml +++ b/config/crd/bases/compliance.openshift.io_scansettings.yaml @@ -38,6 +38,10 @@ spec: debug: description: Enable debug logging of workloads and OpenSCAP type: boolean + disableRemediations: + default: false + description: Defines whether remediations should be allowed to be applied + type: boolean httpsProxy: description: It is recommended to set the proxy via the config.openshift.io/Proxy object Defines a proxy for the scan to get external resources from. diff --git a/pkg/apis/compliance/v1alpha1/compliancesuite_types.go b/pkg/apis/compliance/v1alpha1/compliancesuite_types.go index 75bf348c7..d2f21465b 100644 --- a/pkg/apis/compliance/v1alpha1/compliancesuite_types.go +++ b/pkg/apis/compliance/v1alpha1/compliancesuite_types.go @@ -86,6 +86,9 @@ type ComplianceSuiteSettings struct { // defaulting to False. // +kubebuilder:default=false Suspend bool `json:"suspend,omitempty"` + // Defines whether remediations should be allowed to be applied + // +kubebuilder:default=false + DisableRemediations bool `json:"disableRemediations,omitempty"` } // ComplianceSuiteSpec defines the desired state of ComplianceSuite diff --git a/pkg/controller/complianceremediation/complianceremediation_controller.go b/pkg/controller/complianceremediation/complianceremediation_controller.go index e13269b7c..240c9ddcf 100644 --- a/pkg/controller/complianceremediation/complianceremediation_controller.go +++ b/pkg/controller/complianceremediation/complianceremediation_controller.go @@ -189,6 +189,25 @@ func (r *ReconcileComplianceRemediation) Reconcile(ctx context.Context, request return reconcile.Result{}, nil } +func (r *ReconcileComplianceRemediation) canApplyRemediation(remediationInstance *compv1alpha1.ComplianceRemediation) (bool, error) { + suiteName := remediationInstance.GetLabels()[compv1alpha1.SuiteLabel] + if suiteName == "" { + return false, fmt.Errorf("no suite label found") + } + + suite := &compv1alpha1.ComplianceSuite{} + if err := r.Client.Get(context.TODO(), types.NamespacedName{ + Name: suiteName, Namespace: remediationInstance.Namespace}, suite); err != nil { + return false, fmt.Errorf("couldn't get suite: %w", err) + } + + if suite.Spec.DisableRemediations { + return false, nil + } + + return true, nil +} + // Gets a remediation and ensures the object exists in the cluster if the // remediation if applicable func (r *ReconcileComplianceRemediation) reconcileRemediation(instance *compv1alpha1.ComplianceRemediation, logger logr.Logger) error { @@ -212,10 +231,12 @@ func (r *ReconcileComplianceRemediation) reconcileRemediation(instance *compv1al objectLogger := logger.WithValues("Object.Name", obj.GetName(), "Object.Namespace", obj.GetNamespace(), "Object.Kind", obj.GetKind()) objectLogger.Info("Reconciling remediation object") - + canApplyRemediation, err := r.canApplyRemediation(instance) + if err != nil { + return fmt.Errorf("failed to check if remediation can be applied: %w", err) + } found := obj.DeepCopy() - err := r.Client.Get(context.TODO(), types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, found) - + err = r.Client.Get(context.TODO(), types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, found) if kerrors.IsForbidden(err) { return common.NewNonRetriableCtrlError( "Unable to get fix object from ComplianceRemediation. "+ @@ -234,6 +255,9 @@ func (r *ReconcileComplianceRemediation) reconcileRemediation(instance *compv1al if err != nil { return fmt.Errorf("failed to set related remediations to apply: %w", err) } + if !canApplyRemediation { + return errors.New("We won't create the remediation because DisableRemediations is set to true in the ScanSetting") + } err = r.createRemediation(obj, objectLogger) if err != nil { return fmt.Errorf("failed to create remediation: %w", err) @@ -251,6 +275,9 @@ func (r *ReconcileComplianceRemediation) reconcileRemediation(instance *compv1al if err != nil { return fmt.Errorf("failed to set related remediations to apply: %w", err) } + if !canApplyRemediation { + return errors.New("We won't patch the remediation because DisableRemediations is set to true in the ScanSetting") + } return r.patchRemediation(obj, objectLogger) } err = r.setRemediations(instance, objectLogger, false) diff --git a/tests/e2e/serial/main_test.go b/tests/e2e/serial/main_test.go index b47627e22..28617a25d 100644 --- a/tests/e2e/serial/main_test.go +++ b/tests/e2e/serial/main_test.go @@ -1410,6 +1410,73 @@ func TestKubeletConfigRemediation(t *testing.T) { if remediation.Status.ApplicationState != compv1alpha1.RemediationApplied { t.Fatalf("remediation %s is not applied, but %s", remName, remediation.Status.ApplicationState) } + + // Now we want to update the value of the variable disableRemediations to true in ScanSetting + ssInstance := &compv1alpha1.ScanSetting{} + ssNsName := types.NamespacedName{ + Name: "e2e-default-auto-apply", + Namespace: f.OperatorNamespace, + } + err = f.Client.Get(context.TODO(), ssNsName, ssInstance) + if err != nil { + t.Fatalf("couldn't get scan setting %s: %s", ssNsName.Name, err) + } + + // Update the value + ssInstance.DisableRemediations = true + err = f.Client.Update(context.Background(), ssInstance) + if err != nil { + t.Fatalf("failed to update scan setting %s: %s", ssNsName.Name, err) + } + + // Now we get the tp and update the value + tpInstance := &compv1alpha1.TailoredProfile{} + tpNsName := types.NamespacedName{ + Name: tp.Name, + Namespace: f.OperatorNamespace, + } + err = f.Client.Get(context.TODO(), tpNsName, tpInstance) + if err != nil { + t.Fatalf("couldn't get tailored profile %s: %s", tp.Name, err) + } + + // Update the value + tpInstance.Spec.SetValues[0].Value = "9h0m0s" + err = f.Client.Update(context.Background(), tpInstance) + if err != nil { + t.Fatalf("failed to update tailored profile %s: %s", tp.Name, err) + } + + // Now we re-run the scan + err = f.ReRunScan(scanName, f.OperatorNamespace) + if err != nil { + t.Fatal(err) + } + + // Scan has been re-started + log.Printf("scan phase should be reset") + err = f.WaitForSuiteScansStatus(f.OperatorNamespace, suiteName, compv1alpha1.PhaseRunning, compv1alpha1.ResultNotAvailable) + if err != nil { + t.Fatal(err) + } + + // Ensure that all the scans in the suite have finished and are marked as Done + log.Printf("let's wait for it to be done now") + err = f.WaitForSuiteScansStatus(f.OperatorNamespace, suiteName, compv1alpha1.PhaseDone, compv1alpha1.ResultNonCompliant) + if err != nil { + t.Fatal(err) + } + log.Printf("scan re-run has finished") + + // Now check the remediation is not in error state + err = f.Client.Get(context.TODO(), remNsName, remediation) + if err != nil { + t.Fatalf("couldn't get remediation %s: %s", remName, err) + } + if remediation.Status.ApplicationState != compv1alpha1.RemediationError { + t.Fatalf("remediation %s is not in error state, but %s", remName, remediation.Status.ApplicationState) + } + } func TestSuspendScanSetting(t *testing.T) {