Skip to content

Commit

Permalink
files: perform relabeling from initrd
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jlebon committed Nov 29, 2019
1 parent 9278161 commit 80ca4b2
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 114 deletions.
23 changes: 10 additions & 13 deletions internal/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 }
Expand Down
117 changes: 16 additions & 101 deletions internal/exec/stages/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -32,9 +29,6 @@ import (

const (
name = "files"

// see https://github.com/systemd/systemd/commit/65e183d7899eb3725d3009196ac4decf1090b580
relabelExtraDir = "/run/systemd/relabel-extra.d"
)

var (
Expand Down Expand Up @@ -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
Expand All @@ -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{}
Expand All @@ -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)
}
86 changes: 86 additions & 0 deletions internal/exec/util/selinux.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 80ca4b2

Please sign in to comment.