Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import SElinux module #477 #486

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ var (

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

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

type ErrUnmarshal struct {
Expand Down
30 changes: 30 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 @@ -50,3 +51,32 @@ type GrubUser struct {
Name string `yaml:"name"`
PasswordHash *string `yaml:"password_hash"`
}

type Selinux struct {
Modules []Module `yaml:"modules"`
}

type Module struct {
Name string `yaml:"name"`
Contents Resource `yaml:"contents"`
}

type Resource struct {
Compression *string `yaml:"compression"`
HTTPHeaders HTTPHeaders `yaml:"http_headers"`
Source *string `yaml:"source"`
Inline *string `yaml:"inline"` // Added, not in ignition spec
Local *string `yaml:"local"` // Added, not in ignition spec
Verification Verification `yaml:"verification"`
}

type HTTPHeader struct {
Name string `yaml:"name"`
Value *string `yaml:"value"`
}

type HTTPHeaders []HTTPHeader

type Verification struct {
Hash *string `yaml:"hash"`
}
97 changes: 92 additions & 5 deletions config/fcos/v1_6_exp/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package v1_6_exp
import (
"fmt"
"strings"
"text/template"

baseutil "github.com/coreos/butane/base/util"
"github.com/coreos/butane/config/common"
Expand Down Expand Up @@ -55,6 +56,20 @@ const (
bootV1SizeMiB = 384
)

var (
mountUnitTemplate = template.Must(template.New("unit").Parse(`
# Generated by Butane
[Unit]
Description=Import SELinux module - {{.ModuleName}}
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{.CmdToExecute}}
[Install]
WantedBy=multi-user.target
`))
)

// Return FieldFilters for this spec.
func (c Config) FieldFilters() *cutil.FieldFilters {
return nil
Expand Down Expand Up @@ -85,11 +100,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 +120,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 @@ -376,3 +402,64 @@ 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.Modules {
rendered = processModule(rendered, module, options, ts, r, path.New("yaml", "selinux", "module", i))
}
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.Contents.Inline)), 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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So while this certainly passes the validate, I am having difficulty understanding why this should not be

ts.AddFromCommonSource(yamlPath, path.New("json", "storage","files",len(rendred.storage.files)), rendered.storage.files)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand this better now.

So I do think this is correct. It's saying that everything under and including the .storage key is associated with that module (after adding the index like I mentioned above, it'd be .selinux.module.0 -> .storage and -> .storage.files and so on). Essentially, we're not just adding a file and a unit because of this module, we have to add all the keys leading up to it. All keys in the output need a source translation associated, so e.g. .storage needs to be associated to something on the YAML side.


// Create systemd unit to import module
cmdToExecute := "/usr/sbin/semodule -i" + modulePath
yasminvalim marked this conversation as resolved.
Show resolved Hide resolved

var contents strings.Builder
err = mountUnitTemplate.Execute(&contents, map[string]interface{}{
"ModuleName": module.Name,
"CmdToExecute": cmdToExecute,
})
if err != nil {
r.AddOnError(yamlPath, err)
return rendered
}

result := contents.String()

rendered.Systemd.Units = append(rendered.Systemd.Units, types.Unit{
Name: module.Name + ".conf",
Contents: util.StrToPtr(result),
Enabled: util.BoolToPtr(true),
})
ts.AddFromCommonSource(yamlPath, path.New("json", "systemd"), rendered.Systemd)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So while this certainly passes the validate, I am having difficulty understanding why this should not be

ts.AddFromCommonSource(yamlPath, path.New("json", "systemd","units",len(rendred.systemd.units)), rendered.Systemd.units)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think this is correct too.


return rendered
}
97 changes: 97 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,100 @@ 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", 0), To: path.New("json", "storage")},
{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")},
{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", "systemd", "units", 0)},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd", "units")},
{From: path.New("yaml", "selinux", "module", 0), To: path.New("json", "systemd")},
}
tests := []struct {
in Config
out types.Config
exceptions []translate.Translation
}{
// config with one module
{
Config{
Selinux: Selinux{
Modules: []Module{
{
Name: "some_name",
Contents: Resource{
Inline: util.StrToPtr("some contents 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%20contents%20here"),
Compression: util.StrToPtr(""),
},
},
},
},
},
},
Systemd: types.Systemd{
Units: []types.Unit{
{
Name: "some_name.conf",
Enabled: util.BoolToPtr(true),
Contents: util.StrToPtr(
"\n# Generated by Butane\n" +
"[Unit]\n" +
"Description=Import SELinux module - " + "some_name" + "\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")
})
}

}
2 changes: 1 addition & 1 deletion docs/config-openshift-v4_15.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ The OpenShift configuration is a YAML document conforming to the following speci
* **_ssh_authorized_keys_** (list of strings): a list of SSH keys to be added as an SSH key fragment at `.ssh/authorized_keys.d/ignition` in the user's home directory. All SSH keys must be unique.
* **_ssh_authorized_keys_local_** (list of strings): a list of local paths to SSH key files, relative to the directory specified by the `--files-dir` command-line argument, to be added as SSH key fragments at `.ssh/authorized_keys.d/ignition` in the user's home directory. All SSH keys must be unique. Each file may contain multiple SSH keys, one per line.
* **_boot_device_** (object): describes the desired boot device configuration. At least one of `luks` or `mirror` must be specified.
* **_layout_** (string): the disk layout of the target OS image. Supported values are `aarch64`, `ppc64le`, `s390x-eckd`, `s390x-virt`, `s390x-zfcp`, and `x86_64`. Defaults to `x86_64`.
* **_layout_** (string): the disk layout of the target OS image. Supported values are `aarch64`, `ppc64le`, and `x86_64`. Defaults to `x86_64`.
* **_luks_** (object): describes the clevis configuration for encrypting the root filesystem.
* **_tang_** (list of objects): describes a tang server. Every server must have a unique `url`.
* **url** (string): url of the tang server.
Expand Down
3 changes: 2 additions & 1 deletion docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ nav_order: 9

### Features

- Add SElinux sugar to butane which will allow users import costum SElinux modules.

### Bug fixes

Expand All @@ -33,7 +34,7 @@ key](https://getfedora.org/security/).

### Features

- Support s390x layouts in `boot_device` section (fcos 1.6.0-exp, openshift 4.15.0-exp)
- Support s390x layouts in `boot_device` section (fcos 1.6.0-exp, openshift 4.16.0-exp)
- Stabilize OpenShift spec 4.15.0, targeting Ignition spec 3.4.0
- Add OpenShift spec 4.16.0-experimental, targeting Ignition spec
3.5.0-experimental
Expand Down
13 changes: 12 additions & 1 deletion internal/doc/butane.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ root:
- variant: fcos
min: 1.6.0-experimental
- variant: openshift
min: 4.15.0-experimental
min: 4.16.0-experimental
- name: luks
desc: describes the clevis configuration for encrypting the root filesystem.
children:
Expand Down Expand Up @@ -386,6 +386,17 @@ root:
if:
- variant: openshift
max: 4.15.0
- name: selinux
after: $
desc: simplifies security policy configuration through direct integration with Ignition, facilitating the generation of SELinux modules.
children:
- name: modules
desc: a module retains the data necessary for generating SELinux files.
children:
- name: name
desc: module name
- name: contents
desc: all the information provided that will be the configuration of the SELinux module
- name: openshift
after: $
desc: describes miscellaneous OpenShift configuration. Respected when rendering to a MachineConfig, ignored when rendering directly to an Ignition config.
Expand Down
Loading