Skip to content

Commit

Permalink
fcos/v1_6_exp: Add new sugar for Selinux Modules.
Browse files Browse the repository at this point in the history
  • Loading branch information
yasminvalim committed Dec 14, 2023
1 parent 0a1b18e commit 3940153
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 0 deletions.
4 changes: 4 additions & 0 deletions config/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ var (

// Kernel arguments
ErrGeneralKernelArgumentSupport = errors.New("kernel argument customization is not supported in this spec version")

// Selinux Module
ErrSelinuxContentNotSpecified = errors.New("field \"content\" is required")
ErrSelinuxNameNotSpecified = errors.New("field \"name\" is required")
)

type ErrUnmarshal struct {
Expand Down
27 changes: 27 additions & 0 deletions config/fcos/v1_6_exp/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type Config struct {
base.Config `yaml:",inline"`
BootDevice BootDevice `yaml:"boot_device"`
Grub Grub `yaml:"grub"`
Selinux Selinux `yaml:"selinux"`
Systemd Systemd `yaml:"systemd"`
}

type BootDevice struct {
Expand Down Expand Up @@ -49,3 +51,28 @@ type GrubUser struct {
Name string `yaml:"name"`
PasswordHash *string `yaml:"password_hash"`
}

type Systemd struct {
Units []Unit `yaml:"units"`
}

type Unit struct {
Contents *string `yaml:"contents"`
Dropins []Dropin `yaml:"dropins"`
Enabled *bool `yaml:"enabled"`
Mask *bool `yaml:"mask"`
Name string `yaml:"name"`
}

type Dropin struct {
Contents *string `yaml:"contents"`
Name string `yaml:"name"`
}
type Selinux struct {
Module []Module `yaml:"module"`
}

type Module struct {
Name string `yaml:"name"`
Content string `yaml:"content"`
}
74 changes: 74 additions & 0 deletions config/fcos/v1_6_exp/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,21 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf
}
}

return mergeAndHandleOptions(c, ret, ts, r, options)

}

func mergeAndHandleOptions(c Config, ret types.Config, ts translate.TranslationSet, r report.Report, options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) {
retp, tsp, rp := c.handleUserGrubCfg(options)
retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts)
ret = retConfig.(types.Config)
r.Merge(rp)

retr, trs, rr := c.handleSelinux(options)
returnConfig, ts := baseutil.MergeTranslatedConfigs(retr, trs, ret, ts)
ret = returnConfig.(types.Config)
r.Merge(rr)

return ret, ts, r
}

Expand Down Expand Up @@ -367,3 +378,66 @@ func buildGrubConfig(gb Grub) string {
superUserCmd := fmt.Sprintf("set superusers=\"%s\"\n", strings.Join(allUsers, " "))
return "# Generated by Butane\n\n" + superUserCmd + strings.Join(cmds, "\n") + "\n"
}

func (c Config) handleSelinux(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) {
rendered := types.Config{}
ts := translate.NewTranslationSet("yaml", "json")
var r report.Report

for i, module := range c.Selinux.Module {
yamlPath := path.New("yaml", "selinux", "module", i)
rendered = processModule(rendered, module, options, ts, r, yamlPath)

}
return rendered, ts, r
}

func processModule(rendered types.Config, module Module, options common.TranslateOptions, ts translate.TranslationSet, r report.Report, yamlPath path.ContextPath) types.Config {
src, compression, err := baseutil.MakeDataURL([]byte(module.Content), nil, !options.NoResourceAutoCompression)
if err != nil {
r.AddOnError(yamlPath, err)
return rendered
}

// Create module file
modulePath := fmt.Sprintf("/etc/selinux/targeted/modules/active/extra/%s.cil", module.Name)
newFile := types.File{
Node: types.Node{
Path: modulePath,
},
FileEmbedded1: types.FileEmbedded1{
Append: []types.Resource{
{
Source: util.StrToPtr(src),
Compression: compression,
},
},
},
}

filePath := path.New("json", "storage", "files", len(rendered.Storage.Files), newFile)
rendered.Storage.Files = append(rendered.Storage.Files, newFile)
ts.AddFromCommonSource(yamlPath, filePath, newFile)

// Create systemd unit to import module
cmdToExecute := "/usr/sbin/semodule -i" + modulePath
newUnit := types.Unit{
Name: module.Name + ".conf",
Contents: util.StrToPtr(
"[Unit]\n" +
"Description=Import SELinux module\n" +
"[Service]\n" +
"Type=oneshot\n" +
"RemainAfterExit=yes\n" +
"ExecStart=" + cmdToExecute + "\n" +
"[Install]\n" +
"WantedBy=multi-user.target\n"),
Enabled: util.BoolToPtr(true),
}

unitPath := path.New("json", "systemd", "units", len(rendered.Systemd.Units))
rendered.Systemd.Units = append(rendered.Systemd.Units, newUnit)
ts.AddFromCommonSource(yamlPath, unitPath, newUnit)

return rendered
}
101 changes: 101 additions & 0 deletions config/fcos/v1_6_exp/translate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1637,3 +1637,104 @@ func TestTranslateGrub(t *testing.T) {
})
}
}

func TestTranslateSelinux(t *testing.T) {
cmdToExecute := "/usr/sbin/semodule -i" + "/etc/selinux/targeted/modules/active/extra/some_name.cil"
translations := []translate.Translation{
{From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0)},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0, "name")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0, "contents")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0, "enabled")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0)},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "path")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append", 0)},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append", 0, "source")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append", 0, "compression")},
}
tests := []struct {
in Config
out types.Config
exceptions []translate.Translation
report report.Report
}{
// config with one module
{
Config{
Selinux: Selinux{
Module: []Module{
{
Name: "some_name",
Content: "some content here",
},
},
},
Systemd: Systemd{
Units: []Unit{
{
Name: "some_name.conf",
Enabled: util.BoolToPtr(true),
},
},
},
},

types.Config{
Ignition: types.Ignition{
Version: "3.5.0-experimental",
},
Storage: types.Storage{
Files: []types.File{
{
Node: types.Node{
Path: "/etc/selinux/targeted/modules/active/extra/some_name.cil",
},
FileEmbedded1: types.FileEmbedded1{
Append: []types.Resource{
{
Source: util.StrToPtr("data:,some%20content%20here"),
Compression: util.StrToPtr(""),
},
},
},
},
},
},
Systemd: types.Systemd{
Units: []types.Unit{
{
Name: "some_name" + ".conf",
Enabled: util.BoolToPtr(true),
Contents: util.StrToPtr(
"[Unit]\n" +
"Description=Import SELinux module\n" +
"[Service]\n" +
"Type=oneshot\n" +
"RemainAfterExit=yes\n" +
"ExecStart=" + cmdToExecute + "\n" +
"[Install]\n" +
"WantedBy=multi-user.target\n"),
},
},
},
},
translations,
report.Report{},
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) {
actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{})
r = confutil.TranslateReportPaths(r, translations)
baseutil.VerifyReport(t, test.in, r)
assert.Equal(t, test.out, actual, "translation mismatch")
assert.Equal(t, test.report, r, "report mismatch")
baseutil.VerifyTranslations(t, translations, test.exceptions)
assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage")
})
}
}
17 changes: 17 additions & 0 deletions config/fcos/v1_6_exp/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,20 @@ func (user GrubUser) Validate(c path.ContextPath) (r report.Report) {
}
return
}

func (m Module) Validate(c path.ContextPath) (r report.Report) {
if m.Name == "" && m.Content == "" {
r.AddOnError(c.Append("name"), common.ErrSelinuxContentNotSpecified)
r.AddOnError(c.Append("content"), common.ErrSelinuxContentNotSpecified)
} else {
if m.Name == "" {
r.AddOnError(c.Append("name"), common.ErrSelinuxNameNotSpecified)
}

if m.Content == "" {
r.AddOnError(c.Append("content"), common.ErrSelinuxContentNotSpecified)
}
}

return r
}
46 changes: 46 additions & 0 deletions config/fcos/v1_6_exp/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,49 @@ func TestValidateConfig(t *testing.T) {
})
}
}

func TestValidateModule(t *testing.T) {
tests := []struct {
in Module
out error
errPath path.ContextPath
}{
{
// valid module
in: Module{
Content: "some content",
Name: "some name",
},
out: nil,
errPath: path.New("yaml"),
},
{
// content is not specified
in: Module{
Content: "",
Name: "some name",
},
out: common.ErrSelinuxContentNotSpecified,
errPath: path.New("yaml", "content"),
},
{
// name is not specified
in: Module{
Name: "",
Content: "some content",
},
out: common.ErrSelinuxNameNotSpecified,
errPath: path.New("yaml", "name"),
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) {
actual := test.in.Validate(path.New("yaml"))
baseutil.VerifyReport(t, test.in, actual)
expected := report.Report{}
expected.AddOnError(test.errPath, test.out)
assert.Equal(t, expected, actual, "bad report")
})
}
}

0 comments on commit 3940153

Please sign in to comment.