Skip to content

Commit 310a1f1

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

File tree

6 files changed

+262
-0
lines changed

6 files changed

+262
-0
lines changed

config/common/errors.go

+5
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ var (
9393

9494
// Kernel arguments
9595
ErrGeneralKernelArgumentSupport = errors.New("kernel argument customization is not supported in this spec version")
96+
97+
// Selinux Module
98+
ErrContentInvalid = errors.New("Content is empty, please provide content.")
99+
ErrNameInvalid = errors.New("Name is empty, please provide a valid name.")
100+
ErrFieldInvalid = errors.New("Please, provide valid information.")
96101
)
97102

98103
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

+83
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf
8989
retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts)
9090
ret = retConfig.(types.Config)
9191
r.Merge(rp)
92+
93+
// Clean this as it needs to not be so confusing
94+
retr, trs, rr := c.handleSelinux(options)
95+
returnConfig, ts := baseutil.MergeTranslatedConfigs(retr, trs, ret, ts)
96+
ret = returnConfig.(types.Config)
97+
r.Merge(rr)
98+
9299
return ret, ts, r
93100
}
94101

@@ -367,3 +374,79 @@ func buildGrubConfig(gb Grub) string {
367374
superUserCmd := fmt.Sprintf("set superusers=\"%s\"\n", strings.Join(allUsers, " "))
368375
return "# Generated by Butane\n\n" + superUserCmd + strings.Join(cmds, "\n") + "\n"
369376
}
377+
378+
func (c Config) handleSelinux(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) {
379+
rendered := types.Config{}
380+
ts := translate.NewTranslationSet("yaml", "json")
381+
var r report.Report
382+
383+
for i, module := range c.Selinux.Module {
384+
yamlPath := path.New("yaml", "selinux", "module", i)
385+
if module.Name != "" && module.Content != "" {
386+
rendered = processModule(rendered, module, options, ts, r, yamlPath)
387+
} else {
388+
r.AddOnWarn(yamlPath, common.ErrFieldInvalid)
389+
}
390+
}
391+
392+
if len(rendered.Storage.Files) != 0 {
393+
newFilesystem := types.Filesystem{
394+
Device: "/dev/disk/by-label/boot",
395+
Format: util.StrToPtr("ext4"),
396+
Path: util.StrToPtr("/boot"),
397+
}
398+
399+
rendered.Storage.Filesystems = append(rendered.Storage.Filesystems, newFilesystem)
400+
ts.AddFromCommonSource(path.New("yaml", "selinux", "module", 0), path.New("json", "storage"), newFilesystem)
401+
402+
}
403+
404+
return rendered, ts, r
405+
}
406+
407+
func processModule(rendered types.Config, module Module, options common.TranslateOptions, ts translate.TranslationSet, r report.Report, yamlPath path.ContextPath) types.Config {
408+
src, compression, err := baseutil.MakeDataURL([]byte(module.Content), nil, !options.NoResourceAutoCompression)
409+
if err != nil {
410+
r.AddOnError(yamlPath, err)
411+
return rendered
412+
}
413+
414+
// Create module file
415+
modulePath := fmt.Sprintf("/etc/selinux/targeted/modules/active/extra/%s.cil", module.Name)
416+
newFile := types.File{
417+
Node: types.Node{
418+
Path: modulePath,
419+
},
420+
FileEmbedded1: types.FileEmbedded1{
421+
Append: []types.Resource{
422+
{
423+
Source: util.StrToPtr(src),
424+
Compression: compression,
425+
},
426+
},
427+
},
428+
}
429+
430+
filePath := path.New("json", "storage", "files", len(rendered.Storage.Files), newFile)
431+
rendered.Storage.Files = append(rendered.Storage.Files, newFile)
432+
ts.AddFromCommonSource(yamlPath, filePath, newFile)
433+
434+
// Create systemd unit to import module
435+
cmdToExecute := "/usr/sbin/semodule -i" + modulePath
436+
newUnit := types.Unit{
437+
438+
Enabled: util.BoolToPtr(true),
439+
Dropins: []types.Dropin{
440+
{
441+
Name: module.Name + ".conf",
442+
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"),
443+
},
444+
},
445+
}
446+
447+
unitPath := path.New("json", "systemd", "units", len(rendered.Systemd.Units))
448+
rendered.Systemd.Units = append(rendered.Systemd.Units, newUnit)
449+
ts.AddFromCommonSource(yamlPath, unitPath, newUnit)
450+
451+
return rendered
452+
}

config/fcos/v1_6_exp/translate_test.go

+101
Original file line numberDiff line numberDiff line change
@@ -1637,3 +1637,104 @@ 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", "filesystems")},
1646+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "filesystems", 0)},
1647+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "filesystems", 0, "path")},
1648+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "filesystems", 0, "device")},
1649+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage", "filesystems", 0, "format")},
1650+
{From: path.New("yaml", "selinux", "module"), To: path.New("json", "storage")},
1651+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0)},
1652+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0, "name")},
1653+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0, "dropins")},
1654+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units", 0, "dropins", 0)},
1655+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files")},
1656+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0)},
1657+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "path")},
1658+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append")},
1659+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append", 0)},
1660+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append", 0, "source")},
1661+
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "storage", "files", 0, "append", 0, "compression")},
1662+
}
1663+
tests := []struct {
1664+
in Config
1665+
out types.Config
1666+
exceptions []translate.Translation
1667+
report report.Report
1668+
}{
1669+
// config with one module
1670+
{
1671+
Config{
1672+
Selinux: Selinux{
1673+
Module: []Module{
1674+
{
1675+
Name: "some_name",
1676+
Content: "some content here",
1677+
},
1678+
},
1679+
},
1680+
},
1681+
types.Config{
1682+
Ignition: types.Ignition{
1683+
Version: "3.5.0-experimental",
1684+
},
1685+
Systemd: types.Systemd{
1686+
Units: []types.Unit{
1687+
{
1688+
1689+
Enabled: util.BoolToPtr(true),
1690+
Dropins: []types.Dropin{
1691+
{
1692+
Name: "some_name.conf",
1693+
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"),
1694+
},
1695+
},
1696+
},
1697+
},
1698+
},
1699+
Storage: types.Storage{
1700+
Filesystems: []types.Filesystem{
1701+
{
1702+
Device: "/dev/disk/by-label/boot",
1703+
Format: util.StrToPtr("ext4"),
1704+
Path: util.StrToPtr("/boot"),
1705+
},
1706+
},
1707+
Files: []types.File{
1708+
{
1709+
Node: types.Node{
1710+
Path: "/etc/selinux/targeted/modules/active/extra/some_name.cil",
1711+
},
1712+
FileEmbedded1: types.FileEmbedded1{
1713+
Append: []types.Resource{
1714+
{
1715+
Source: util.StrToPtr("data:,some%20content%20here"),
1716+
Compression: util.StrToPtr(""),
1717+
},
1718+
},
1719+
},
1720+
},
1721+
},
1722+
},
1723+
},
1724+
translations,
1725+
report.Report{},
1726+
},
1727+
}
1728+
1729+
for i, test := range tests {
1730+
t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) {
1731+
actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{})
1732+
r = confutil.TranslateReportPaths(r, translations)
1733+
baseutil.VerifyReport(t, test.in, r)
1734+
assert.Equal(t, test.out, actual, "translation mismatch")
1735+
assert.Equal(t, test.report, r, "report mismatch")
1736+
baseutil.VerifyTranslations(t, translations, test.exceptions)
1737+
assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage")
1738+
})
1739+
}
1740+
}

config/fcos/v1_6_exp/validate.go

+11
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,14 @@ 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("content"), common.ErrContentInvalid)
84+
}
85+
86+
if m.Content != "" && m.Name == "" {
87+
r.AddOnError(c.Append("name"), common.ErrNameInvalid)
88+
}
89+
return r
90+
}

config/fcos/v1_6_exp/validate_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,55 @@ 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+
// content is empty, path is specified
491+
in: Module{
492+
Content: "",
493+
Name: "some name",
494+
},
495+
out: common.ErrContentInvalid,
496+
errPath: path.New("yaml", "content"),
497+
},
498+
{
499+
// name is empty, content is specified
500+
in: Module{
501+
Name: "",
502+
Content: "some content",
503+
},
504+
out: common.ErrNameInvalid,
505+
errPath: path.New("yaml", "name"),
506+
},
507+
{
508+
// name and content are empty
509+
in: Module{},
510+
out: nil,
511+
errPath: path.New("yaml"),
512+
},
513+
{
514+
// name and content are specified
515+
in: Module{
516+
Content: "some content",
517+
Name: "some name",
518+
},
519+
out: nil,
520+
errPath: path.New("yaml"),
521+
},
522+
}
523+
524+
for i, test := range tests {
525+
t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) {
526+
actual := test.in.Validate(path.New("yaml"))
527+
baseutil.VerifyReport(t, test.in, actual)
528+
expected := report.Report{}
529+
expected.AddOnError(test.errPath, test.out)
530+
assert.Equal(t, expected, actual, "bad report")
531+
})
532+
}
533+
}

0 commit comments

Comments
 (0)