Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenSCAP json tailoring (HMS-3826) #798

Merged
merged 5 commits into from
Jul 29, 2024
Merged
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
10 changes: 5 additions & 5 deletions pkg/blueprint/customizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,18 @@ type ServicesCustomization struct {
}

type OpenSCAPCustomization struct {
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
Tailoring *OpenSCAPTailoringCustomizations `json:"tailoring,omitempty" toml:"tailoring,omitempty"`
XMLTailoring *OpenSCAPXMLTailoringCustomizations `json:"xml_tailoring,omitempty" toml:"xml_tailoring,omitempty"`
kingsleyzissou marked this conversation as resolved.
Show resolved Hide resolved
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
Tailoring *OpenSCAPTailoringCustomizations `json:"tailoring,omitempty" toml:"tailoring,omitempty"`
JSONTailoring *OpenSCAPJSONTailoringCustomizations `json:"json_tailoring,omitempty" toml:"json_tailoring,omitempty"`
}

type OpenSCAPTailoringCustomizations struct {
Selected []string `json:"selected,omitempty" toml:"selected,omitempty"`
Unselected []string `json:"unselected,omitempty" toml:"unselected,omitempty"`
}

type OpenSCAPXMLTailoringCustomizations struct {
type OpenSCAPJSONTailoringCustomizations struct {
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
Filepath string `json:"filepath,omitempty" toml:"filepath,omitempty"`
}
Expand Down
110 changes: 61 additions & 49 deletions pkg/customizations/oscap/oscap.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,74 +57,86 @@ type RemediationConfig struct {
type TailoringConfig struct {
RemediationConfig
TailoredProfileID string
JSONFilepath string
Selected []string
Unselected []string
}

func NewConfigs(oscapConfig blueprint.OpenSCAPCustomization, defaultDatastream *string) (*RemediationConfig, *TailoringConfig, error) {
var datastream = oscapConfig.DataStream
if datastream == "" {
if defaultDatastream == nil {
return nil, nil, fmt.Errorf("No OSCAP datastream specified and the distro does not have any default set")
}
datastream = *defaultDatastream
}

remediationConfig := &RemediationConfig{
Datastream: datastream,
ProfileID: oscapConfig.ProfileID,
CompressionEnabled: true,
}
func newTailoringConfigs(profileID string, tc blueprint.OpenSCAPTailoringCustomizations, remediationConfig RemediationConfig) (*RemediationConfig, *TailoringConfig, error) {
// the tailoring config needs to know about the original base profile id,
// but the remediation config needs to know the updated profile id.
remediationConfig.ProfileID = fmt.Sprintf("%s_osbuild_tailoring", remediationConfig.ProfileID)
remediationConfig.TailoringPath = filepath.Join(DataDir, "tailoring.xml")

if oscapConfig.XMLTailoring != nil && oscapConfig.Tailoring != nil {
return nil, nil, fmt.Errorf("Either XML tailoring file and profile ID must be set or custom rules (selected/unselected), not both")
tailoringConfig := &TailoringConfig{
RemediationConfig: RemediationConfig{
ProfileID: profileID,
Datastream: remediationConfig.Datastream,
TailoringPath: remediationConfig.TailoringPath,
},
Selected: tc.Selected,
Unselected: tc.Unselected,
TailoredProfileID: remediationConfig.ProfileID,
}

if xmlConfigs := oscapConfig.XMLTailoring; xmlConfigs != nil {
if xmlConfigs.Filepath == "" {
return nil, nil, fmt.Errorf("Filepath to an XML tailoring file is required")
}

if xmlConfigs.ProfileID == "" {
return nil, nil, fmt.Errorf("Tailoring profile ID is required for an XML tailoring file")
}

remediationConfig.ProfileID = xmlConfigs.ProfileID
remediationConfig.TailoringPath = xmlConfigs.Filepath
return &remediationConfig, tailoringConfig, nil
}

// since the XML tailoring file has already been provided
// we don't need the autotailor stage and the config can
// be left empty and we can just return the `remediationConfig`
return remediationConfig, nil, nil
func newJsonConfigs(profileID string, json blueprint.OpenSCAPJSONTailoringCustomizations, remediationConfig RemediationConfig) (*RemediationConfig, *TailoringConfig, error) {
if json.Filepath == "" {
return nil, nil, fmt.Errorf("Filepath to an JSON tailoring file is required")
}

tc := oscapConfig.Tailoring
if tc == nil {
return remediationConfig, nil, nil
if json.ProfileID == "" {
return nil, nil, fmt.Errorf("Tailoring profile ID is required for an JSON tailoring file")
}

tailoringPath := filepath.Join(DataDir, "tailoring.xml")
tailoredProfileID := fmt.Sprintf("%s_osbuild_tailoring", remediationConfig.ProfileID)
// the tailoring config needs to know about the original base profile id,
// but the remediation config needs to know the updated profile id.
remediationConfig.ProfileID = json.ProfileID
remediationConfig.TailoringPath = filepath.Join(DataDir, "tailoring.xml")

tailoringConfig := &TailoringConfig{
RemediationConfig: RemediationConfig{
ProfileID: remediationConfig.ProfileID,
TailoringPath: tailoringPath,
Datastream: datastream,
ProfileID: profileID,
Datastream: remediationConfig.Datastream,
TailoringPath: remediationConfig.TailoringPath,
},
TailoredProfileID: tailoredProfileID,
Selected: tc.Selected,
Unselected: tc.Unselected,
JSONFilepath: json.Filepath,
TailoredProfileID: json.ProfileID,
}

return &remediationConfig, tailoringConfig, nil
}

func NewConfigs(oscapConfig blueprint.OpenSCAPCustomization, defaultDatastream *string) (*RemediationConfig, *TailoringConfig, error) {
var datastream = oscapConfig.DataStream
if datastream == "" {
if defaultDatastream == nil {
return nil, nil, fmt.Errorf("No OSCAP datastream specified and the distro does not have any default set")
}
datastream = *defaultDatastream
}

// the reason for changing the remediation config profile
// after we create the tailoring configs is that the tailoring
// config needs to know about the original base profile id, but
// the remediation config needs to know the updated profile id.
remediationConfig.ProfileID = tailoredProfileID
remediationConfig.TailoringPath = tailoringPath
tc := oscapConfig.Tailoring
json := oscapConfig.JSONTailoring

return remediationConfig, tailoringConfig, nil
remediationConfig := RemediationConfig{
Datastream: datastream,
ProfileID: oscapConfig.ProfileID,
CompressionEnabled: true,
}

switch {
case tc != nil && json != nil:
return nil, nil, fmt.Errorf("Multiple tailoring types set, only one type can be chosen (JSON/Override rules)")
case tc != nil:
return newTailoringConfigs(oscapConfig.ProfileID, *tc, remediationConfig)
case json != nil:
return newJsonConfigs(oscapConfig.ProfileID, *json, remediationConfig)
default:
return &remediationConfig, nil, nil
}
}

func DefaultFedoraDatastream() string {
Expand Down
69 changes: 54 additions & 15 deletions pkg/osbuild/oscap_autotailor_stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,57 @@ type OscapAutotailorStageOptions struct {
Config OscapAutotailorConfig `json:"config"`
}

type OscapAutotailorConfig struct {
TailoredProfileID string `json:"new_profile"`
Datastream string `json:"datastream"`
ProfileID string `json:"profile_id"`
Selected []string `json:"selected,omitempty"`
Unselected []string `json:"unselected,omitempty"`
type OscapAutotailorConfig interface {
validate() error
isAutotailorConfig()
}

func (OscapAutotailorStageOptions) isStageOptions() {}
type AutotailorKeyValueConfig struct {
NewProfile string `json:"new_profile"`
Datastream string `json:"datastream"`
ProfileID string `json:"profile_id"`
Selected []string `json:"selected,omitempty"`
Unselected []string `json:"unselected,omitempty"`
}

func (c AutotailorKeyValueConfig) isAutotailorConfig() {}

func (c OscapAutotailorConfig) validate() error {
func (c AutotailorKeyValueConfig) validate() error {
if c.Datastream == "" {
return fmt.Errorf("'datastream' must be specified")
}
if c.NewProfile == "" {
return fmt.Errorf("'new_profile' must be specified")
}
kingsleyzissou marked this conversation as resolved.
Show resolved Hide resolved
if c.ProfileID == "" {
return fmt.Errorf("'profile_id' must be specified")
}
return nil
}

type AutotailorJSONConfig struct {
TailoredProfileID string `json:"tailored_profile_id"`
Datastream string `json:"datastream"`
TailoringFile string `json:"tailoring_file"`
}

func (c AutotailorJSONConfig) isAutotailorConfig() {}

func (c AutotailorJSONConfig) validate() error {
if c.Datastream == "" {
return fmt.Errorf("'datastream' must be specified")
}
if c.TailoredProfileID == "" {
return fmt.Errorf("'new_profile' must be specified")
return fmt.Errorf("'tailored_profile_id' must be specified")
}
if c.TailoringFile == "" {
return fmt.Errorf("'tailoring_file' must be specified")
}
return nil
}

func (OscapAutotailorStageOptions) isStageOptions() {}

func NewOscapAutotailorStage(options *OscapAutotailorStageOptions) *Stage {
if err := options.Config.validate(); err != nil {
panic(err)
Expand All @@ -56,14 +84,25 @@ func NewOscapAutotailorStageOptions(options *oscap.TailoringConfig) *OscapAutota
panic(fmt.Errorf("The tailoring path for the OpenSCAP remediation config cannot be empty, this is a programming error"))
}

if options.JSONFilepath != "" {
return &OscapAutotailorStageOptions{
Filepath: options.RemediationConfig.TailoringPath,
Config: AutotailorJSONConfig{
TailoredProfileID: options.TailoredProfileID,
Datastream: options.RemediationConfig.Datastream,
TailoringFile: options.JSONFilepath,
},
}
}

return &OscapAutotailorStageOptions{
Filepath: options.RemediationConfig.TailoringPath,
Config: OscapAutotailorConfig{
TailoredProfileID: options.TailoredProfileID,
Datastream: options.RemediationConfig.Datastream,
ProfileID: options.RemediationConfig.ProfileID,
Selected: options.Selected,
Unselected: options.Unselected,
Config: AutotailorKeyValueConfig{
NewProfile: options.TailoredProfileID,
Datastream: options.RemediationConfig.Datastream,
ProfileID: options.RemediationConfig.ProfileID,
Selected: options.Selected,
Unselected: options.Unselected,
},
}
}
62 changes: 47 additions & 15 deletions pkg/osbuild/oscap_autotailor_stage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import (
func TestNewOscapAutotailorStage(t *testing.T) {
stageOptions := &OscapAutotailorStageOptions{
Filepath: "tailoring.xml",
Config: OscapAutotailorConfig{
Datastream: "test_stream",
ProfileID: "test_profile",
TailoredProfileID: "test_profile_osbuild_profile",
Selected: []string{"fast_rule"},
Unselected: []string{"slow_rule"},
Config: AutotailorKeyValueConfig{
Datastream: "test_stream",
ProfileID: "test_profile",
NewProfile: "test_profile_osbuild_profile",
Selected: []string{"fast_rule"},
Unselected: []string{"slow_rule"},
},
}

Expand All @@ -33,14 +33,16 @@ func TestOscapAutotailorStageOptionsValidate(t *testing.T) {
err bool
}{
{
name: "empty-options",
options: OscapAutotailorStageOptions{},
err: true,
name: "empty-options",
options: OscapAutotailorStageOptions{
Config: AutotailorKeyValueConfig{},
},
err: true,
},
{
name: "empty-datastream",
options: OscapAutotailorStageOptions{
Config: OscapAutotailorConfig{
Config: AutotailorKeyValueConfig{
ProfileID: "test-profile",
},
},
Expand All @@ -49,7 +51,7 @@ func TestOscapAutotailorStageOptionsValidate(t *testing.T) {
{
name: "empty-profile-id",
options: OscapAutotailorStageOptions{
Config: OscapAutotailorConfig{
Config: AutotailorKeyValueConfig{
Datastream: "test-datastream",
},
},
Expand All @@ -58,20 +60,50 @@ func TestOscapAutotailorStageOptionsValidate(t *testing.T) {
{
name: "empty-new-profile-name",
options: OscapAutotailorStageOptions{
Config: OscapAutotailorConfig{
Config: AutotailorKeyValueConfig{
ProfileID: "test-profile",
Datastream: "test-datastream",
},
},
err: true,
},
{
name: "empty-tailored-profile-id",
options: OscapAutotailorStageOptions{
Config: AutotailorKeyValueConfig{
Datastream: "test-datastream",
},
},
err: true,
},
{
name: "empty-json-filepath",
options: OscapAutotailorStageOptions{
Config: AutotailorJSONConfig{
Datastream: "test-datastream",
TailoredProfileID: "test-tailored-id",
},
},
err: true,
},
{
name: "valid-data",
options: OscapAutotailorStageOptions{
Config: OscapAutotailorConfig{
ProfileID: "test-profile",
Config: AutotailorKeyValueConfig{
ProfileID: "test-profile",
Datastream: "test-datastream",
NewProfile: "test-profile-osbuild-profile",
},
},
err: false,
},
{
name: "valid-data-json-config",
options: OscapAutotailorStageOptions{
Config: AutotailorJSONConfig{
Datastream: "test-datastream",
TailoredProfileID: "test-profile-osbuild-profile",
TailoredProfileID: "test-profile",
TailoringFile: "/test/filepath.json",
},
},
err: false,
Expand Down
10 changes: 1 addition & 9 deletions test/config-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,6 @@
"ami"
]
},
"./configs/oscap-rhel8-with-xml-tailoring.json": {
"distros": [
"rhel-8.10"
],
"image-types": [
"ami"
]
},
"./configs/oscap-rhel9.json": {
"distros": [
"rhel-9.4"
Expand All @@ -212,7 +204,7 @@
"ami"
]
},
"./configs/oscap-rhel9-with-xml-tailoring.json": {
"./configs/oscap-rhel9-with-json-tailoring.json": {
"distros": [
"rhel-9.4"
],
Expand Down
Loading
Loading