diff --git a/pkg/apis/compliance/v1alpha1/rule_types.go b/pkg/apis/compliance/v1alpha1/rule_types.go index 527cba620..533296e74 100644 --- a/pkg/apis/compliance/v1alpha1/rule_types.go +++ b/pkg/apis/compliance/v1alpha1/rule_types.go @@ -18,6 +18,9 @@ const RuleHideTagAnnotationKey = "compliance.openshift.io/hide-tag" // RuleVariableAnnotationKey store list of xccdf variables used to render the rule const RuleVariableAnnotationKey = "compliance.openshift.io/rule-variable" +// RuleProfileAnnotationKey is the annotation used to store which profiles are using a particular rule +const RuleProfileAnnotationKey = "compliance.openshift.io/profiles" + const ( CheckTypePlatform = "Platform" CheckTypeNode = "Node" diff --git a/pkg/profileparser/profileparser.go b/pkg/profileparser/profileparser.go index a56a9ddd9..394b2234e 100644 --- a/pkg/profileparser/profileparser.go +++ b/pkg/profileparser/profileparser.go @@ -548,6 +548,7 @@ func ParseRulesAndDo(contentDom *xmlquery.Node, stdParser *referenceParser, pb * var wg sync.WaitGroup questionsTable := utils.NewOcilQuestionTable(contentDom) defTable := utils.NewDefHashTable(contentDom) + profileTable := utils.NewProfileTable(contentDom) allValues := xmlquery.Find(contentDom, "//xccdf-1.2:Value") valuesList := make(map[string]string) @@ -585,6 +586,7 @@ func ParseRulesAndDo(contentDom *xmlquery.Node, stdParser *referenceParser, pb * rationale := ruleObj.SelectElement("xccdf-1.2:rationale") warnings := utils.GetWarningsForRule(ruleObj) severity := ruleObj.SelectAttr("severity") + profiles := utils.GetRuleProfile(ruleObj, profileTable) fixes := []cmpv1alpha1.FixDefinition{} foundPlatformMap := make(map[string]bool) @@ -640,6 +642,23 @@ func ParseRulesAndDo(contentDom *xmlquery.Node, stdParser *referenceParser, pb * annotations[cmpv1alpha1.RuleHideTagAnnotationKey] = "true" } + profileList := []string{} + + // add profiles to annotations + if len(profiles) > 0 { + for _, profile := range profiles { + profileID := profile.SelectAttr("id") + if profileID == "" { + log.Info("no id in profile") + continue + } + profileList = append(profileList, GetPrefixedName(pb.Name, xccdf.GetProfileNameFromID(profileID))) + } + if len(profileList) > 0 { + annotations[cmpv1alpha1.RuleProfileAnnotationKey] = strings.Join(profileList, ",") + } + } + p := cmpv1alpha1.Rule{ TypeMeta: metav1.TypeMeta{ Kind: "Rule", diff --git a/pkg/utils/parse_arf_result.go b/pkg/utils/parse_arf_result.go index 251645e27..4c019782d 100644 --- a/pkg/utils/parse_arf_result.go +++ b/pkg/utils/parse_arf_result.go @@ -192,6 +192,11 @@ func newRuleHashTable(dsDom *xmlquery.Node) NodeByIdHashTable { func NewOcilQuestionTable(dsDom *xmlquery.Node) NodeByIdHashTable { return newHashTableFromRootAndQuery(dsDom, "//ds:component/ocil:ocil", "//ocil:boolean_question") } + +func NewProfileTable(dsDom *xmlquery.Node) NodeByIdHashTable { + return newHashTableFromRootAndQuery(dsDom, "//ds:component/xccdf-1.2:Benchmark", "//xccdf-1.2:Profile") +} + func newStateHashTable(dsDom *xmlquery.Node) NodeByIdHashTable { return newHashTableFromRootAndQuery(dsDom, "//ds:component/oval-def:oval_definitions/oval-def:states", "*") } @@ -308,6 +313,21 @@ func findAllVariablesFromObject(node *xmlquery.Node) ([]string, bool) { } } +func GetRuleProfile(rule *xmlquery.Node, profileTable NodeByIdHashTable) NodeByIdHashTable { + // loop through profile table to find which profile uses the rule + ruleProfile := make(NodeByIdHashTable) + for _, profile := range profileTable { + for _, selectRule := range profile.SelectElements("//xccdf-1.2:select") { + if selectRule.SelectAttr("idref") == rule.SelectAttr("id") { + if selectRule.SelectAttr("selected") == "true" { + ruleProfile[profile.SelectAttr("id")] = profile + } + } + } + } + return ruleProfile +} + func GetRuleOvalTest(rule *xmlquery.Node, defTable NodeByIdHashTable) NodeByIdHashTable { var ovalRefEl *xmlquery.Node testList := make(map[string]*xmlquery.Node) diff --git a/tests/e2e/framework/common.go b/tests/e2e/framework/common.go index bdebd1a82..e07d2fa69 100644 --- a/tests/e2e/framework/common.go +++ b/tests/e2e/framework/common.go @@ -820,6 +820,17 @@ func (f *Framework) WaitForScanSettingBindingStatus(namespace, name string, targ return nil } +func (f *Framework) AssertProfileInRuleAnnotation(r *compv1alpha1.Rule, expectedProfileId string) bool { + if r.Annotations == nil { + return false + } + profileIds := r.Annotations[compv1alpha1.RuleProfileAnnotationKey] + if profileIds == "" { + return false + } + return strings.Contains(profileIds, expectedProfileId) +} + // 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 dd611aa6d..5151ac808 100644 --- a/tests/e2e/parallel/main_test.go +++ b/tests/e2e/parallel/main_test.go @@ -2827,3 +2827,33 @@ func TestResultServerHTTPVersion(t *testing.T) { } } } + +func TestRuleHasProfileAnnotation(t *testing.T) { + t.Parallel() + f := framework.Global + const requiredRule = "ocp4-file-groupowner-worker-kubeconfig" + const expectedRuleProfileAnnotation = "ocp4-pci-dss-node,ocp4-moderate-node,ocp4-stig-node,ocp4-nerc-cip-node,ocp4-cis-node,ocp4-high-node" + err, found := f.DoesRuleExist(f.OperatorNamespace, requiredRule) + if err != nil { + t.Fatal(err) + } else if !found { + t.Fatalf("Expected rule %s not found", requiredRule) + } + + // Check if requiredRule has the correct profile annotation + rule := &compv1alpha1.Rule{} + err = f.Client.Get(context.TODO(), types.NamespacedName{ + Name: requiredRule, + Namespace: f.OperatorNamespace, + }, rule) + if err != nil { + t.Fatal(err) + } + expectedProfiles := strings.Split(expectedRuleProfileAnnotation, ",") + for _, profileName := range expectedProfiles { + if !f.AssertProfileInRuleAnnotation(rule, profileName) { + t.Fatalf("expected to find profile %s in rule %s", profileName, rule.Name) + } + } + +}