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 19, 2023
1 parent 0a1b18e commit 6f9bf5f
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 5 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
10 changes: 10 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,7 @@ type Config struct {
base.Config `yaml:",inline"`
BootDevice BootDevice `yaml:"boot_device"`
Grub Grub `yaml:"grub"`
Selinux Selinux `yaml:"selinux"`
}

type BootDevice struct {
Expand Down Expand Up @@ -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"`
}
78 changes: 73 additions & 5 deletions config/fcos/v1_6_exp/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,8 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf
}
}

retp, tsp, rp := c.handleUserGrubCfg(options)
retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts)
ret = retConfig.(types.Config)
r.Merge(rp)
return ret, ts, r
return mergeAndHandleOptions(c, ret, ts, r, options)

}

// ToIgn3_5 translates the config to an Ignition config. It returns a
Expand All @@ -108,6 +105,20 @@ func ToIgn3_5Bytes(input []byte, options common.TranslateBytesOptions) ([]byte,
return cutil.TranslateBytes(input, &Config{}, "ToIgn3_5", 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
}

func (c Config) processBootDevice(config *types.Config, ts *translate.TranslationSet, options common.TranslateOptions) report.Report {
var rendered types.Config
renderedTranslations := translate.NewTranslationSet("yaml", "json")
Expand Down Expand Up @@ -367,3 +378,60 @@ 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 _, module := range c.Selinux.Module {
rendered = processModule(rendered, module, options, ts, r, path.New("yaml", "selinux", "module"))
}
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)

rendered.Storage.Files = append(rendered.Storage.Files, types.File{
Node: types.Node{
Path: modulePath,
},
FileEmbedded1: types.FileEmbedded1{
Append: []types.Resource{
{
Source: util.StrToPtr(src),
Compression: compression,
},
},
},
})
ts.AddFromCommonSource(yamlPath, path.New("json", "storage"), rendered.Storage)

// Create systemd unit to import module
cmdToExecute := "/usr/sbin/semodule -i" + modulePath

rendered.Systemd.Units = append(rendered.Systemd.Units, 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),
})
ts.AddFromCommonSource(yamlPath, path.New("json", "systemd"), rendered.Systemd)

return rendered
}
94 changes: 94 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,97 @@ 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"), To: path.New("json", "storage", "files")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0)},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "path")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append", 0)},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append", 0, "source")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append", 0, "compression")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0, "name")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0, "contents")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0, "enabled")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0)},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units")},
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd")},
}
tests := []struct {
in Config
out types.Config
exceptions []translate.Translation
}{
// 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",
},
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,
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) {
out, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{})
r = confutil.TranslateReportPaths(r, translations)
baseutil.VerifyReport(t, test.in, r)
assert.Equal(t, test.out, out, "bad output")
assert.Equal(t, report.Report{}, r, "expected empty report")
baseutil.VerifyTranslations(t, translations, test.exceptions)
assert.NoError(t, translations.DebugVerifyCoverage(out), "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 6f9bf5f

Please sign in to comment.