Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkg/apis/compliance/v1alpha1/profilebundle_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const ProfileImageDigestAnnotation = "compliance.openshift.io/image-digest"
// ProfileStatusAnnotation is the parsed out status from the data stream
const ProfileStatusAnnotation = "compliance.openshift.io/profile-status"

// XCCDFGroupsAnnotation stores a comma-separated list of all XCCDF Group IDs
// found in the datastream. Used for re-enabling groups in TailoredProfiles.
const XCCDFGroupsAnnotation = "compliance.openshift.io/xccdf-groups"

// DataStreamStatusType is the type for the data stream status
type DataStreamStatusType string

Expand Down
46 changes: 46 additions & 0 deletions pkg/profileparser/profileparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ func GetPrefixedName(pbName, objName string) string {
}

func ParseBundle(contentDom *xmlquery.Node, pb *cmpv1alpha1.ProfileBundle, pcfg *ParserConfig) error {
// Extract all XCCDF Group IDs from the datastream and store in ProfileBundle annotation
if err := extractAndStoreXCCDFGroups(contentDom, pb, pcfg); err != nil {
log.Error(err, "Failed to extract XCCDF groups")
// Don't fail the whole parse if group extraction fails
}

// One go routine per type
errChan := make(chan error)
done := make(chan string)
Expand Down Expand Up @@ -950,3 +956,43 @@ func appendKeyWithSep(annotations map[string]string, key, item, sep string) {
}
annotations[key] = strings.Join(append(curList, item), sep)
}

// extractAndStoreXCCDFGroups extracts all XCCDF Group IDs from the datastream
// and stores them as a comma-separated list in the ProfileBundle annotation.
// This allows TailoredProfiles to re-enable all groups when extending a parent profile.
func extractAndStoreXCCDFGroups(contentDom *xmlquery.Node, pb *cmpv1alpha1.ProfileBundle, pcfg *ParserConfig) error {
// Find all Group elements in the datastream
groupNodes := xmlquery.Find(contentDom, "//xccdf-1.2:Group")
if len(groupNodes) == 0 {
log.Info("No XCCDF groups found in datastream")
return nil
}

groupIDs := make([]string, 0, len(groupNodes))
for _, groupNode := range groupNodes {
id := groupNode.SelectAttr("id")
if id != "" {
groupIDs = append(groupIDs, id)
}
}

if len(groupIDs) == 0 {
return nil
}

// Store as comma-separated list in ProfileBundle annotation
annotations := pb.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations[cmpv1alpha1.XCCDFGroupsAnnotation] = strings.Join(groupIDs, ",")
pb.SetAnnotations(annotations)

// Update the ProfileBundle with the new annotation
if err := pcfg.Client.Update(context.TODO(), pb); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be Patch to avoid updating the entire object?

return fmt.Errorf("failed to update ProfileBundle with XCCDF groups: %w", err)
}

log.Info("Extracted XCCDF groups", "count", len(groupIDs), "profileBundle", pb.Name)
return nil
}
25 changes: 23 additions & 2 deletions pkg/xccdf/tailoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,19 @@ func getSelectElementFromCRRule(rule *cmpv1alpha1.Rule, enable bool) SelectEleme
}
}

func getSelections(tp *cmpv1alpha1.TailoredProfile, rules map[string]*cmpv1alpha1.Rule) []SelectElement {
func getSelections(tp *cmpv1alpha1.TailoredProfile, rules map[string]*cmpv1alpha1.Rule, groupIDs []string) []SelectElement {
selections := []SelectElement{}

// When extending a profile, enable all XCCDF groups first
// This allows individual rules within deselected groups to be enabled
// Groups are enabled before rules so OpenSCAP processes them in the correct order
for _, groupID := range groupIDs {
selections = append(selections, SelectElement{
IDRef: groupID,
Selected: true,
})
}

for _, selection := range tp.Spec.EnableRules {
rule := rules[selection.Name]
selections = append(selections, getSelectElementFromCRRule(rule, true))
Expand Down Expand Up @@ -200,6 +211,16 @@ func getValuesFromVariables(variables []*cmpv1alpha1.Variable) []SetValueElement

// TailoredProfileToXML gets an XML string from a TailoredProfile and the corresponding Profile
func TailoredProfileToXML(tp *cmpv1alpha1.TailoredProfile, p *cmpv1alpha1.Profile, pb *cmpv1alpha1.ProfileBundle, rules map[string]*cmpv1alpha1.Rule, variables []*cmpv1alpha1.Variable) (string, error) {
// Extract group IDs from ProfileBundle annotation if this TP extends a profile
var groupIDs []string
if p != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also check if pb is nil?

if pb.Annotations != nil {
if groupsStr, ok := pb.Annotations[cmpv1alpha1.XCCDFGroupsAnnotation]; ok && groupsStr != "" {
groupIDs = strings.Split(groupsStr, ",")
}
}
}

tailoring := TailoringElement{
XMLNamespaceURI: XCCDFURI,
ID: getTailoringID(tp),
Expand All @@ -215,7 +236,7 @@ func TailoredProfileToXML(tp *cmpv1alpha1.TailoredProfile, p *cmpv1alpha1.Profil
},
Profile: ProfileElement{
ID: GetXCCDFProfileID(tp),
Selections: getSelections(tp, rules),
Selections: getSelections(tp, rules, groupIDs),
Values: getValuesFromVariables(variables),
},
}
Expand Down
Loading