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

OpenSCAP tailoring: add key/value rule overrides #300

Closed
4 changes: 2 additions & 2 deletions Schutzfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"fedora-38": {
"dependencies": {
"osbuild": {
"commit": "f3d740aaf8531e55b99632f579f2fae13f1511b7"
"commit": "0767ebccc120eb4a43e274fec64eb95646a66761"
}
},
"repos": [
Expand Down Expand Up @@ -76,4 +76,4 @@
}
]
}
}
}
18 changes: 0 additions & 18 deletions pkg/blueprint/customizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,6 @@ type ServicesCustomization struct {
Disabled []string `json:"disabled,omitempty" toml:"disabled,omitempty"`
}

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"`
}

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

type CustomizationError struct {
Message string
}
Expand Down Expand Up @@ -316,13 +305,6 @@ func (c *Customizations) GetFDO() *FDOCustomization {
return c.FDO
}

func (c *Customizations) GetOpenSCAP() *OpenSCAPCustomization {
if c == nil {
return nil
}
return c.OpenSCAP
}

func (c *Customizations) GetIgnition() *IgnitionCustomization {
if c == nil {
return nil
Expand Down
20 changes: 0 additions & 20 deletions pkg/blueprint/customizations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,23 +371,3 @@ func TestGetFilesystemsMinSizeNonSectorSize(t *testing.T) {

assert.EqualValues(t, uint64(5632), retFilesystemsSize)
}

func TestGetOpenSCAPConfig(t *testing.T) {

expectedOscap := OpenSCAPCustomization{
DataStream: "test-data-stream.xml",
ProfileID: "test_profile",
Tailoring: &OpenSCAPTailoringCustomizations{
Selected: []string{"quick_rule"},
Unselected: []string{"very_slow_rule"},
},
}

TestCustomizations := Customizations{
OpenSCAP: &expectedOscap,
}

retOpenSCAPCustomiztions := TestCustomizations.GetOpenSCAP()

assert.EqualValues(t, expectedOscap, *retOpenSCAPCustomiztions)
}
78 changes: 78 additions & 0 deletions pkg/blueprint/openscap_customizations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package blueprint

import (
"encoding/json"
"fmt"
)

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"`
}

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

type OpenSCAPTailoringOverride struct {
Var string `json:"var,omitempty" toml:"var,omitempty"`
Value interface{} `json:"value,omitempty" toml:"value,omitempty"`
}

func (c *Customizations) GetOpenSCAP() *OpenSCAPCustomization {
if c == nil {
return nil
}
return c.OpenSCAP
}

func (ot *OpenSCAPTailoringOverride) UnmarshalTOML(data interface{}) error {
d, _ := data.(map[string]interface{})

switch d["var"].(type) {
case string:
ot.Var = d["var"].(string)
default:
return fmt.Errorf("TOML unmarshal: override var must be string, got %v of type %T", d["var"], d["var"])
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: you could use this instead of specifying the same value twice:

return fmt.Errorf("TOML unmarshal: override var must be string, got %[1]v of type %[1]T", d["var"])

and basically in all similar cases in this file.

}

switch d["value"].(type) {
case int64:
ot.Value = uint64(d["value"].(int64))
case string:
ot.Value = d["value"].(string)
default:
return fmt.Errorf("TOML unmarshal: override value must be integer or string, got %v of type %T", d["value"], d["value"])
}

return nil
}

func (ot *OpenSCAPTailoringOverride) UnmarshalJSON(data []byte) error {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
d, _ := v.(map[string]interface{})

switch d["var"].(type) {
case string:
ot.Var = d["var"].(string)
default:
return fmt.Errorf("JSON unmarshal: override var must be string, got %v of type %T", d["var"], d["var"])
}

switch d["value"].(type) {
case float64:
ot.Value = uint64(d["value"].(float64))
case string:
ot.Value = d["value"].(string)
default:
return fmt.Errorf("JSON unmarshal: override var must be float64 number or string, got %v of type %T", d["value"], d["value"])
}

return nil
}
143 changes: 143 additions & 0 deletions pkg/blueprint/openscap_customizations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package blueprint

import (
"encoding/json"
"testing"

"github.com/BurntSushi/toml"
"github.com/stretchr/testify/assert"
)

func TestGetOpenSCAPConfig(t *testing.T) {

expectedOscap := OpenSCAPCustomization{
Datastream: "test-data-stream.xml",
ProfileID: "test_profile",
Tailoring: &OpenSCAPTailoringCustomizations{
Selected: []string{"quick_rule"},
Unselected: []string{"very_slow_rule"},
Overrides: []OpenSCAPTailoringOverride{
OpenSCAPTailoringOverride{
Var: "rule_id",
Value: 50,
},
},
},
}

TestCustomizations := Customizations{
OpenSCAP: &expectedOscap,
}

retOpenSCAPCustomiztions := TestCustomizations.GetOpenSCAP()

assert.EqualValues(t, expectedOscap, *retOpenSCAPCustomiztions)
}

func TestOpenSCAPOverrideTOMLUnmarshaler(t *testing.T) {
tests := []struct {
name string
TOML string
want *OpenSCAPTailoringOverride
wantErr bool
}{
{
name: "string based rule",
TOML: `
var = "sshd_idle_timeout_value"
value = "600"
`,
want: &OpenSCAPTailoringOverride{
Var: "sshd_idle_timeout_value",
Value: "600",
},
wantErr: false,
},
{
name: "integer based rule",
TOML: `
var = "sshd_idle_timeout_value"
value = 600
`,
want: &OpenSCAPTailoringOverride{
Var: "sshd_idle_timeout_value",
Value: uint64(600),
},
wantErr: false,
},
{
name: "invalid rule",
TOML: `
var = "sshd_idle_timeout_value"
`,
want: nil,
wantErr: true,
},
}

for _, tt := range tests {
var override OpenSCAPTailoringOverride
err := toml.Unmarshal([]byte(tt.TOML), &override)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotNil(t, override)
assert.Equal(t, tt.want, &override)
}
}
}

func TestOpenSCAPOverrideJSONUnmarshaler(t *testing.T) {
tests := []struct {
name string
JSON string
want *OpenSCAPTailoringOverride
wantErr bool
}{
{
name: "string based rule",
JSON: `{
"var": "sshd_idle_timeout_value",
"value": "600"
}`,
want: &OpenSCAPTailoringOverride{
Var: "sshd_idle_timeout_value",
Value: "600",
},
wantErr: false,
},
{
name: "integer based rule",
JSON: `{
"var": "sshd_idle_timeout_value",
"value": 600
}`,
want: &OpenSCAPTailoringOverride{
Var: "sshd_idle_timeout_value",
Value: uint64(600),
},
wantErr: false,
},
{
name: "invalid rule",
JSON: `{
"var": "sshd_idle_timeout_value"
}`,
want: nil,
wantErr: true,
},
}

for _, tt := range tests {
var override OpenSCAPTailoringOverride
err := json.Unmarshal([]byte(tt.JSON), &override)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotNil(t, override)
assert.Equal(t, tt.want, &override)
}
}
}
52 changes: 26 additions & 26 deletions pkg/customizations/oscap/oscap.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package oscap

import (
"fmt"
"path/filepath"
"strings"

"github.com/osbuild/images/pkg/customizations/fsnode"
"github.com/osbuild/images/pkg/distro"
)

type Profile string
Expand Down Expand Up @@ -40,28 +38,42 @@ const (
defaultRHEL8Datastream string = "/usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml"
defaultRHEL9Datastream string = "/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml"

// tailoring directory path
// directory paths
dataDirPath string = "/oscap_data"
tailoringDirPath string = "/usr/share/xml/osbuild-openscap-data"
)

func DefaultFedoraDatastream() string {
return defaultFedoraDatastream
}
func getDatastream(datastream string, d distro.Distro) string {
if datastream != "" {
return datastream
}

func DefaultRHEL8Datastream(isRHEL bool) string {
if isRHEL {
return defaultRHEL8Datastream
s := strings.ToLower(d.Name())
if strings.HasPrefix(s, "fedora") {
return defaultFedoraDatastream
}

if strings.HasPrefix(s, "centos") {
return defaultCentosDatastream(d.Releasever())
}
return defaultCentos8Datastream

return defaultRHELDatastream(d.Releasever())
Copy link
Member

Choose a reason for hiding this comment

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

Just a thought, not sure if of any value: Would it make sense to check also rhel as the prefix and maybe panic() in the default case when the distro is not explicitly supported?

}

func DefaultRHEL9Datastream(isRHEL bool) string {
if isRHEL {
return defaultRHEL9Datastream
func defaultCentosDatastream(releaseVer string) string {
if releaseVer == "8" {
return defaultCentos8Datastream
}
return defaultCentos9Datastream
}

func defaultRHELDatastream(releaseVer string) string {
if releaseVer == "8" {
return defaultRHEL8Datastream
}
return defaultRHEL9Datastream
}

func IsProfileAllowed(profile string, allowlist []Profile) bool {
for _, a := range allowlist {
if a.String() == profile {
Expand All @@ -77,15 +89,3 @@ func IsProfileAllowed(profile string, allowlist []Profile) bool {

return false
}

func GetTailoringFile(profile string) (string, string, *fsnode.Directory, error) {
newProfile := fmt.Sprintf("%s_osbuild_tailoring", profile)
path := filepath.Join(tailoringDirPath, "tailoring.xml")

tailoringDir, err := fsnode.NewDirectory(tailoringDirPath, nil, nil, nil, true)
if err != nil {
return "", "", nil, err
}

return newProfile, path, tailoringDir, nil
}
Loading
Loading