Skip to content

Commit 6f9bf5f

Browse files
committed
fcos/v1_6_exp: Add new sugar for Selinux Modules.
1 parent 0a1b18e commit 6f9bf5f

File tree

6 files changed

+244
-5
lines changed

6 files changed

+244
-5
lines changed

config/common/errors.go

+4
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ var (
9393

9494
// Kernel arguments
9595
ErrGeneralKernelArgumentSupport = errors.New("kernel argument customization is not supported in this spec version")
96+
97+
// Selinux Module
98+
ErrSelinuxContentNotSpecified = errors.New("field \"content\" is required")
99+
ErrSelinuxNameNotSpecified = errors.New("field \"name\" is required")
96100
)
97101

98102
type ErrUnmarshal struct {

config/fcos/v1_6_exp/schema.go

+10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Config struct {
2222
base.Config `yaml:",inline"`
2323
BootDevice BootDevice `yaml:"boot_device"`
2424
Grub Grub `yaml:"grub"`
25+
Selinux Selinux `yaml:"selinux"`
2526
}
2627

2728
type BootDevice struct {
@@ -49,3 +50,12 @@ type GrubUser struct {
4950
Name string `yaml:"name"`
5051
PasswordHash *string `yaml:"password_hash"`
5152
}
53+
54+
type Selinux struct {
55+
Module []Module `yaml:"module"`
56+
}
57+
58+
type Module struct {
59+
Name string `yaml:"name"`
60+
Content string `yaml:"content"`
61+
}

config/fcos/v1_6_exp/translate.go

+73-5
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,8 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf
8585
}
8686
}
8787

88-
retp, tsp, rp := c.handleUserGrubCfg(options)
89-
retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts)
90-
ret = retConfig.(types.Config)
91-
r.Merge(rp)
92-
return ret, ts, r
88+
return mergeAndHandleOptions(c, ret, ts, r, options)
89+
9390
}
9491

9592
// ToIgn3_5 translates the config to an Ignition config. It returns a
@@ -108,6 +105,20 @@ func ToIgn3_5Bytes(input []byte, options common.TranslateBytesOptions) ([]byte,
108105
return cutil.TranslateBytes(input, &Config{}, "ToIgn3_5", options)
109106
}
110107

108+
func mergeAndHandleOptions(c Config, ret types.Config, ts translate.TranslationSet, r report.Report, options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) {
109+
retp, tsp, rp := c.handleUserGrubCfg(options)
110+
retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts)
111+
ret = retConfig.(types.Config)
112+
r.Merge(rp)
113+
114+
retr, trs, rr := c.handleSelinux(options)
115+
returnConfig, ts := baseutil.MergeTranslatedConfigs(retr, trs, ret, ts)
116+
ret = returnConfig.(types.Config)
117+
r.Merge(rr)
118+
119+
return ret, ts, r
120+
}
121+
111122
func (c Config) processBootDevice(config *types.Config, ts *translate.TranslationSet, options common.TranslateOptions) report.Report {
112123
var rendered types.Config
113124
renderedTranslations := translate.NewTranslationSet("yaml", "json")
@@ -367,3 +378,60 @@ func buildGrubConfig(gb Grub) string {
367378
superUserCmd := fmt.Sprintf("set superusers=\"%s\"\n", strings.Join(allUsers, " "))
368379
return "# Generated by Butane\n\n" + superUserCmd + strings.Join(cmds, "\n") + "\n"
369380
}
381+
382+
func (c Config) handleSelinux(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) {
383+
rendered := types.Config{}
384+
ts := translate.NewTranslationSet("yaml", "json")
385+
var r report.Report
386+
387+
for _, module := range c.Selinux.Module {
388+
rendered = processModule(rendered, module, options, ts, r, path.New("yaml", "selinux", "module"))
389+
}
390+
return rendered, ts, r
391+
}
392+
393+
func processModule(rendered types.Config, module Module, options common.TranslateOptions, ts translate.TranslationSet, r report.Report, yamlPath path.ContextPath) types.Config {
394+
src, compression, err := baseutil.MakeDataURL([]byte(module.Content), nil, !options.NoResourceAutoCompression)
395+
if err != nil {
396+
r.AddOnError(yamlPath, err)
397+
return rendered
398+
}
399+
400+
// Create module file
401+
modulePath := fmt.Sprintf("/etc/selinux/targeted/modules/active/extra/%s.cil", module.Name)
402+
403+
rendered.Storage.Files = append(rendered.Storage.Files, types.File{
404+
Node: types.Node{
405+
Path: modulePath,
406+
},
407+
FileEmbedded1: types.FileEmbedded1{
408+
Append: []types.Resource{
409+
{
410+
Source: util.StrToPtr(src),
411+
Compression: compression,
412+
},
413+
},
414+
},
415+
})
416+
ts.AddFromCommonSource(yamlPath, path.New("json", "storage"), rendered.Storage)
417+
418+
// Create systemd unit to import module
419+
cmdToExecute := "/usr/sbin/semodule -i" + modulePath
420+
421+
rendered.Systemd.Units = append(rendered.Systemd.Units, types.Unit{
422+
Name: module.Name + ".conf",
423+
Contents: util.StrToPtr(
424+
"[Unit]\n" +
425+
"Description=Import SELinux module\n" +
426+
"[Service]\n" +
427+
"Type=oneshot\n" +
428+
"RemainAfterExit=yes\n" +
429+
"ExecStart=" + cmdToExecute + "\n" +
430+
"[Install]\n" +
431+
"WantedBy=multi-user.target\n"),
432+
Enabled: util.BoolToPtr(true),
433+
})
434+
ts.AddFromCommonSource(yamlPath, path.New("json", "systemd"), rendered.Systemd)
435+
436+
return rendered
437+
}

config/fcos/v1_6_exp/translate_test.go

+94
Original file line numberDiff line numberDiff line change
@@ -1637,3 +1637,97 @@ func TestTranslateGrub(t *testing.T) {
16371637
})
16381638
}
16391639
}
1640+
1641+
func TestTranslateSelinux(t *testing.T) {
1642+
cmdToExecute := "/usr/sbin/semodule -i" + "/etc/selinux/targeted/modules/active/extra/some_name.cil"
1643+
translations := []translate.Translation{
1644+
{From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")},
1645+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage")},
1646+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files")},
1647+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0)},
1648+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "path")},
1649+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append")},
1650+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append", 0)},
1651+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append", 0, "source")},
1652+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "files", 0, "append", 0, "compression")},
1653+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0, "name")},
1654+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0, "contents")},
1655+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0, "enabled")},
1656+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units", 0)},
1657+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd", "units")},
1658+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "systemd")},
1659+
}
1660+
tests := []struct {
1661+
in Config
1662+
out types.Config
1663+
exceptions []translate.Translation
1664+
}{
1665+
// config with one module
1666+
{
1667+
Config{
1668+
Selinux: Selinux{
1669+
Module: []Module{
1670+
{
1671+
Name: "some_name",
1672+
Content: "some content here",
1673+
},
1674+
},
1675+
},
1676+
},
1677+
1678+
types.Config{
1679+
Ignition: types.Ignition{
1680+
Version: "3.5.0-experimental",
1681+
},
1682+
Storage: types.Storage{
1683+
Files: []types.File{
1684+
{
1685+
Node: types.Node{
1686+
Path: "/etc/selinux/targeted/modules/active/extra/some_name.cil",
1687+
},
1688+
FileEmbedded1: types.FileEmbedded1{
1689+
Append: []types.Resource{
1690+
{
1691+
Source: util.StrToPtr("data:,some%20content%20here"),
1692+
Compression: util.StrToPtr(""),
1693+
},
1694+
},
1695+
},
1696+
},
1697+
},
1698+
},
1699+
Systemd: types.Systemd{
1700+
Units: []types.Unit{
1701+
{
1702+
Name: "some_name" + ".conf",
1703+
Enabled: util.BoolToPtr(true),
1704+
Contents: util.StrToPtr(
1705+
"[Unit]\n" +
1706+
"Description=Import SELinux module\n" +
1707+
"[Service]\n" +
1708+
"Type=oneshot\n" +
1709+
"RemainAfterExit=yes\n" +
1710+
"ExecStart=" + cmdToExecute + "\n" +
1711+
"[Install]\n" +
1712+
"WantedBy=multi-user.target\n"),
1713+
},
1714+
},
1715+
},
1716+
},
1717+
translations,
1718+
},
1719+
}
1720+
1721+
for i, test := range tests {
1722+
t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) {
1723+
out, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{})
1724+
r = confutil.TranslateReportPaths(r, translations)
1725+
baseutil.VerifyReport(t, test.in, r)
1726+
assert.Equal(t, test.out, out, "bad output")
1727+
assert.Equal(t, report.Report{}, r, "expected empty report")
1728+
baseutil.VerifyTranslations(t, translations, test.exceptions)
1729+
assert.NoError(t, translations.DebugVerifyCoverage(out), "incomplete TranslationSet coverage")
1730+
})
1731+
}
1732+
1733+
}

config/fcos/v1_6_exp/validate.go

+17
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,20 @@ func (user GrubUser) Validate(c path.ContextPath) (r report.Report) {
7777
}
7878
return
7979
}
80+
81+
func (m Module) Validate(c path.ContextPath) (r report.Report) {
82+
if m.Name == "" && m.Content == "" {
83+
r.AddOnError(c.Append("name"), common.ErrSelinuxContentNotSpecified)
84+
r.AddOnError(c.Append("content"), common.ErrSelinuxContentNotSpecified)
85+
} else {
86+
if m.Name == "" {
87+
r.AddOnError(c.Append("name"), common.ErrSelinuxNameNotSpecified)
88+
}
89+
90+
if m.Content == "" {
91+
r.AddOnError(c.Append("content"), common.ErrSelinuxContentNotSpecified)
92+
}
93+
}
94+
95+
return r
96+
}

config/fcos/v1_6_exp/validate_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,49 @@ func TestValidateConfig(t *testing.T) {
479479
})
480480
}
481481
}
482+
483+
func TestValidateModule(t *testing.T) {
484+
tests := []struct {
485+
in Module
486+
out error
487+
errPath path.ContextPath
488+
}{
489+
{
490+
// valid module
491+
in: Module{
492+
Content: "some content",
493+
Name: "some name",
494+
},
495+
out: nil,
496+
errPath: path.New("yaml"),
497+
},
498+
{
499+
// content is not specified
500+
in: Module{
501+
Content: "",
502+
Name: "some name",
503+
},
504+
out: common.ErrSelinuxContentNotSpecified,
505+
errPath: path.New("yaml", "content"),
506+
},
507+
{
508+
// name is not specified
509+
in: Module{
510+
Name: "",
511+
Content: "some content",
512+
},
513+
out: common.ErrSelinuxNameNotSpecified,
514+
errPath: path.New("yaml", "name"),
515+
},
516+
}
517+
518+
for i, test := range tests {
519+
t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) {
520+
actual := test.in.Validate(path.New("yaml"))
521+
baseutil.VerifyReport(t, test.in, actual)
522+
expected := report.Report{}
523+
expected.AddOnError(test.errPath, test.out)
524+
assert.Equal(t, expected, actual, "bad report")
525+
})
526+
}
527+
}

0 commit comments

Comments
 (0)