From 80ca4b2f834007ea13762cec5f9df43fd7061d00 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Fri, 29 Nov 2019 10:19:00 -0500 Subject: [PATCH] files: perform relabeling from initrd Now that we can relabel from the initrd, we should be able to categorically resolve all SELinux and Ignition issues. Here, we drop the hacky `ignition-relabel.service` and relabel all the files we need to on the spot! Requires: https://lore.kernel.org/selinux/20190819193032.848-1-jlebon@redhat.com/ Closes: #635 --- internal/distro/distro.go | 23 +++--- internal/exec/stages/files/files.go | 117 ++++------------------------ internal/exec/util/selinux.go | 86 ++++++++++++++++++++ 3 files changed, 112 insertions(+), 114 deletions(-) create mode 100644 internal/exec/util/selinux.go diff --git a/internal/distro/distro.go b/internal/distro/distro.go index de18a9ce1..9eba49670 100644 --- a/internal/distro/distro.go +++ b/internal/distro/distro.go @@ -41,10 +41,7 @@ var ( udevadmCmd = "udevadm" usermodCmd = "usermod" useraddCmd = "useradd" - - // The restorecon tool is embedded inside of a systemd unit - // and as such requires the absolute path - restoreconCmd = "/usr/sbin/restorecon" + setfilesCmd = "setfiles" // Filesystem tools btrfsMkfsCmd = "mkfs.btrfs" @@ -75,15 +72,15 @@ func DiskByPartUUIDDir() string { return diskByPartUUIDDir } func KernelCmdlinePath() string { return kernelCmdlinePath } func SystemConfigDir() string { return fromEnv("SYSTEM_CONFIG_DIR", systemConfigDir) } -func GroupaddCmd() string { return groupaddCmd } -func MdadmCmd() string { return mdadmCmd } -func MountCmd() string { return mountCmd } -func SgdiskCmd() string { return sgdiskCmd } -func ModprobeCmd() string { return modprobeCmd } -func UdevadmCmd() string { return udevadmCmd } -func UsermodCmd() string { return usermodCmd } -func UseraddCmd() string { return useraddCmd } -func RestoreconCmd() string { return restoreconCmd } +func GroupaddCmd() string { return groupaddCmd } +func MdadmCmd() string { return mdadmCmd } +func MountCmd() string { return mountCmd } +func SgdiskCmd() string { return sgdiskCmd } +func ModprobeCmd() string { return modprobeCmd } +func UdevadmCmd() string { return udevadmCmd } +func UsermodCmd() string { return usermodCmd } +func UseraddCmd() string { return useraddCmd } +func SetfilesCmd() string { return setfilesCmd } func BtrfsMkfsCmd() string { return btrfsMkfsCmd } func Ext4MkfsCmd() string { return ext4MkfsCmd } diff --git a/internal/exec/stages/files/files.go b/internal/exec/stages/files/files.go index 071582da1..5ad7029cb 100644 --- a/internal/exec/stages/files/files.go +++ b/internal/exec/stages/files/files.go @@ -17,10 +17,7 @@ package files import ( "errors" "fmt" - "io/ioutil" - "os" "path/filepath" - "strings" "github.com/coreos/ignition/v2/config/v3_1_experimental/types" "github.com/coreos/ignition/v2/internal/distro" @@ -32,9 +29,6 @@ import ( const ( name = "files" - - // see https://github.com/systemd/systemd/commit/65e183d7899eb3725d3009196ac4decf1090b580 - relabelExtraDir = "/run/systemd/relabel-extra.d" ) var ( @@ -87,17 +81,8 @@ func (s stage) Run(config types.Config) error { return fmt.Errorf("failed to create units: %v", err) } - // add systemd unit to relabel files - if err := s.addRelabelUnit(); err != nil { - return fmt.Errorf("failed to add relabel unit: %v", err) - } - - // Add a file in /run/systemd/relabel-extra.d/ with paths that need to be relabeled - // as early as possible (e.g. systemd units so systemd can read them while building its - // graph). These are relabeled very early (right after policy load) so it cannot relabel - // across mounts. Only relabel things in /etc here. - if err := s.addRelabelExtraFile(); err != nil { - return fmt.Errorf("failed to write systemd relabel file: %v", err) + if err := s.relabelFiles(); err != nil { + return fmt.Errorf("failed to handle relabeling: %v", err) } return nil @@ -106,23 +91,11 @@ func (s stage) Run(config types.Config) error { // checkRelabeling determines whether relabeling is supported/requested so that // we only collect filenames if we need to. func (s *stage) checkRelabeling() error { - if !distro.SelinuxRelabel() || distro.RestoreconCmd() == "" { + if !distro.SelinuxRelabel() { s.Logger.Debug("compiled without relabeling support, skipping") return nil } - path, err := s.JoinPath(distro.RestoreconCmd()) - if err != nil { - return fmt.Errorf("error resolving path for %s: %v", distro.RestoreconCmd(), err) - } - - _, err = os.Lstat(path) - if err != nil && os.IsNotExist(err) { - return fmt.Errorf("targeting root without %s, cannot relabel", distro.RestoreconCmd()) - } else if err != nil { - return fmt.Errorf("error checking for %s in root: %v", distro.RestoreconCmd(), err) - } - // initialize to non-nil (whereas a nil slice means not to append, even // though they're functionally equivalent) s.toRelabel = []string{} @@ -137,82 +110,24 @@ func (s *stage) relabeling() bool { // relabel adds one or more paths to the list of paths that need relabeling. func (s *stage) relabel(paths ...string) { if s.toRelabel != nil { - s.toRelabel = append(s.toRelabel, paths...) + for _, path := range paths { + s.toRelabel = append(s.toRelabel, filepath.Join(s.DestDir, path)) + } } } -// addRelabelUnit creates and enables a runtime systemd unit to run restorecon -// if there are files that need to be relabeled. -func (s *stage) addRelabelUnit() error { - if len(s.toRelabel) == 0 { +// relabelFiles relabels all the files that were marked for relabeling using +// the libselinux APIs. +func (s *stage) relabelFiles() error { + if s.toRelabel == nil || len(s.toRelabel) == 0 { return nil } - contents := `[Unit] -Description=Relabel files created by Ignition -DefaultDependencies=no -After=local-fs.target -Before=sysinit.target systemd-sysctl.service -ConditionSecurity=selinux -ConditionPathExists=/etc/selinux/ignition.relabel -OnFailure=emergency.target -OnFailureJobMode=replace-irreversibly - -[Service] -Type=oneshot -ExecStart=` + distro.RestoreconCmd() + ` -0vRif /etc/selinux/ignition.relabel -ExecStart=/usr/bin/rm /etc/selinux/ignition.relabel -RemainAfterExit=yes` - - // create the unit file itself - unit := types.Unit{ - Name: "ignition-relabel.service", - Contents: &contents, - } - if err := s.writeSystemdUnit(unit, true); err != nil { - return err - } - - if err := s.EnableRuntimeUnit(unit, "sysinit.target"); err != nil { - return err - } - - // and now create the list of files to relabel - etcRelabelPath, err := s.JoinPath("etc/selinux/ignition.relabel") - if err != nil { - return err - } - f, err := os.Create(etcRelabelPath) - if err != nil { - return err - } - defer f.Close() - - // yes, apparently the final \0 is needed - _, err = f.WriteString(strings.Join(s.toRelabel, "\000") + "\000") - return err -} - -// addRelabelExtraFile writes a file to /run/systemd/relabel-extra.d/ with a list of files -// that should be relabeled immediately after policy load. In our case that's everything we -// wrote under /etc. This ensures systemd can access the files when building it's graph. -func (s stage) addRelabelExtraFile() error { - relabelFilePath := filepath.Join(relabelExtraDir, "ignition.relabel") - s.Logger.Info("adding relabel-extra.d/ file: %q", relabelFilePath) - defer s.Logger.Info("finished adding relabel file") - - relabelFileContents := "" - for _, file := range s.toRelabel { - if strings.HasPrefix(file, "/etc") { - relabelFileContents += file + "\n" - } - } - if relabelFileContents == "" { - return nil - } - if err := os.MkdirAll(relabelExtraDir, 0755); err != nil { - return err - } + // We could go further here and use the `setfscreatecon` API so that we + // atomically create the files from the start with the right label, but (1) + // atomicity isn't really necessary here since there is not even a policy + // loaded and hence no MAC enforced, and (2) we'd still need after-the-fact + // labeling for files created by processes we call out to, like `useradd`. - return ioutil.WriteFile(relabelFilePath, []byte(relabelFileContents), 0644) + return s.RelabelFiles(s.toRelabel) } diff --git a/internal/exec/util/selinux.go b/internal/exec/util/selinux.go new file mode 100644 index 000000000..22ffe78c1 --- /dev/null +++ b/internal/exec/util/selinux.go @@ -0,0 +1,86 @@ +// Copyright 2019 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/coreos/ignition/v2/internal/distro" +) + +const ( + selinuxConfig = "/etc/selinux/config" + selinuxFileContexts = "contexts/files/file_contexts" +) + +var selinuxPolicy = "" + +func (ut Util) getSelinuxPolicy() (string, error) { + if selinuxPolicy == "" { + configPath, err := ut.JoinPath(selinuxConfig) + if err != nil { + return "", err + } + + file, err := os.Open(configPath) + if err != nil { + return "", fmt.Errorf("failed to open %v: %v", selinuxConfig, err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, "SELINUXTYPE=") { + policy := line[len("SELINUXTYPE="):] + if len(policy) == 0 { + return "", fmt.Errorf("invalid SELINUXTYPE value in %v", selinuxConfig) + } + selinuxPolicy = policy + break + } + } + + if selinuxPolicy == "" { + return "", fmt.Errorf("didn't find SELINUXTYPE in %v", selinuxConfig) + } + } + + return selinuxPolicy, nil +} + +// RelabelFiles relabels all the files matching the globby patterns given. +func (ut Util) RelabelFiles(patterns []string) error { + policy, err := ut.getSelinuxPolicy() + if err != nil { + return err + } + + file_contexts, err := ut.JoinPath("/etc/selinux", policy, selinuxFileContexts) + if err != nil { + return err + } + + cmd := exec.Command(distro.SetfilesCmd(), "-vFi0", "-r", ut.DestDir, file_contexts, "-f", "-") + cmd.Stdin = strings.NewReader(strings.Join(patterns, "\000") + "\000") + if _, err := ut.Logger.LogCmd(cmd, "relabeling %d patterns", len(patterns)); err != nil { + return err + } + return nil +}