From 30a7dfa5eb266e3e4fb8bbcf104a1a8cff9dedae Mon Sep 17 00:00:00 2001 From: Yasmin Valim Date: Fri, 8 Sep 2023 17:49:15 -0300 Subject: [PATCH] fcos/v1_6_exp: Add new sugar for Selinux Modules. --- config/common/errors.go | 4 ++ config/fcos/v1_6_exp/schema.go | 10 +++ config/fcos/v1_6_exp/translate.go | 70 ++++++++++++++++++++ config/fcos/v1_6_exp/translate_test.go | 89 ++++++++++++++++++++++++++ config/fcos/v1_6_exp/validate.go | 17 +++++ config/fcos/v1_6_exp/validate_test.go | 46 +++++++++++++ 6 files changed, 236 insertions(+) diff --git a/config/common/errors.go b/config/common/errors.go index a5f8d5b2..f50279a3 100644 --- a/config/common/errors.go +++ b/config/common/errors.go @@ -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 { diff --git a/config/fcos/v1_6_exp/schema.go b/config/fcos/v1_6_exp/schema.go index 140cd31a..9644179f 100644 --- a/config/fcos/v1_6_exp/schema.go +++ b/config/fcos/v1_6_exp/schema.go @@ -22,6 +22,7 @@ type Config struct { base.Config `yaml:",inline"` BootDevice BootDevice `yaml:"boot_device"` Grub Grub `yaml:"grub"` + Selinux Selinux `yaml:"selinux"` } type BootDevice struct { @@ -49,3 +50,12 @@ type GrubUser struct { Name string `yaml:"name"` PasswordHash *string `yaml:"password_hash"` } + +type Selinux struct { + Module []Module `yaml:"module"` +} + +type Module struct { + Name string `yaml:"name"` + Content string `yaml:"content"` +} diff --git a/config/fcos/v1_6_exp/translate.go b/config/fcos/v1_6_exp/translate.go index 2a45287b..b07e5efd 100644 --- a/config/fcos/v1_6_exp/translate.go +++ b/config/fcos/v1_6_exp/translate.go @@ -89,6 +89,13 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts) ret = retConfig.(types.Config) r.Merge(rp) + + // Clean this as it needs to not be so confusing + 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 } @@ -367,3 +374,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 +} diff --git a/config/fcos/v1_6_exp/translate_test.go b/config/fcos/v1_6_exp/translate_test.go index 87620a77..9f2d8cae 100644 --- a/config/fcos/v1_6_exp/translate_test.go +++ b/config/fcos/v1_6_exp/translate_test.go @@ -1637,3 +1637,92 @@ 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, "dropins")}, + {From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0, "dropins", 0)}, + {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", + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0-experimental", + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Name: "selinux-import-module@.service", + Enabled: util.BoolToPtr(true), + Dropins: []types.Dropin{ + { + Name: "some_name.conf", + Contents: util.StrToPtr(" [Unit]\n" + "Description=Import SELinux module\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=no\n" + "ExecStart=" + cmdToExecute + "\n" + "[Install]\n" + "WantedBy=multi-user.target\n"), + }, + }, + }, + }, + }, + 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(""), + }, + }, + }, + }, + }, + }, + }, + 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") + }) + } +} diff --git a/config/fcos/v1_6_exp/validate.go b/config/fcos/v1_6_exp/validate.go index 4c3ae9de..e952202f 100644 --- a/config/fcos/v1_6_exp/validate.go +++ b/config/fcos/v1_6_exp/validate.go @@ -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 +} diff --git a/config/fcos/v1_6_exp/validate_test.go b/config/fcos/v1_6_exp/validate_test.go index 2c850580..19df624a 100644 --- a/config/fcos/v1_6_exp/validate_test.go +++ b/config/fcos/v1_6_exp/validate_test.go @@ -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") + }) + } +}