diff --git a/Dockerfile b/Dockerfile index 53d76b25044..033c03d9b3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,8 @@ RUN ARCH=$(uname -m); \ xorriso \ cosign \ gptfdisk \ + patterns-microos-selinux \ + btrfsprogs \ lvm2 && \ zypper cc -a diff --git a/examples/green/Dockerfile b/examples/green/Dockerfile index 4eb49f57378..0c88e06db54 100644 --- a/examples/green/Dockerfile +++ b/examples/green/Dockerfile @@ -57,9 +57,10 @@ RUN ARCH=$(uname -m); \ sudo \ curl \ sed \ - patch \ iproute2 \ podman \ + audit \ + patterns-microos-selinux \ btrfsprogs \ btrfsmaintenance \ snapper \ @@ -79,6 +80,9 @@ RUN echo "PermitRootLogin yes" > /etc/ssh/sshd_config.d/rootlogin.conf # Add default network configuration ADD 05_network.yaml /system/oem/05_network.yaml +# SELinux in enforce mode +RUN sed -i "s|SELINUX=.*|SELINUX=enforcing|g" /etc/selinux/config + # Add default snapshotter setup ADD snapshotter.yaml /etc/elemental/config.d/snapshotter.yaml diff --git a/pkg/action/build-disk.go b/pkg/action/build-disk.go index db8d87ed653..6281219784c 100644 --- a/pkg/action/build-disk.go +++ b/pkg/action/build-disk.go @@ -105,8 +105,7 @@ func (b *BuildDiskAction) buildDiskHook(hook string) error { } func (b *BuildDiskAction) buildDiskChrootHook(hook string, root string) error { - extraMounts := map[string]string{} - return ChrootHook(&b.cfg.Config, hook, b.cfg.Strict, root, extraMounts, b.cfg.CloudInitPaths...) + return ChrootHook(&b.cfg.Config, hook, b.cfg.Strict, root, nil, b.cfg.CloudInitPaths...) } func (b *BuildDiskAction) preparePartitionsRoot() error { @@ -219,12 +218,6 @@ func (b *BuildDiskAction) BuildDiskRun() (err error) { //nolint:gocyclo return elementalError.NewFromError(err, elementalError.SetDefaultGrubEntry) } - // Relabel SELinux - err = b.applySelinuxLabels(recRoot, b.spec.Expandable) - if err != nil { - return elementalError.NewFromError(err, elementalError.SelinuxRelabel) - } - // After disk hook happens after deploying the OS tree into a temporary folder if !b.spec.Expandable { err = b.buildDiskChrootHook(constants.AfterDiskChrootHook, recRoot) @@ -699,19 +692,6 @@ func (b *BuildDiskAction) CreateDiskPartitionTable(disk string) error { return nil } -// applySelinuxLabels sets SELinux extended attributes to the root-tree being installed. Swallows errors, label on a best effort -func (b *BuildDiskAction) applySelinuxLabels(root string, unprivileged bool) error { - if unprivileged { - // Swallow errors, label on a best effort when not chrooting - _ = elemental.SelinuxRelabel(b.cfg.Config, root) - return nil - } - binds := map[string]string{} - return utils.ChrootedCallback( - &b.cfg.Config, root, binds, func() error { return elemental.SelinuxRelabel(b.cfg.Config, "/") }, - ) -} - func (b *BuildDiskAction) createBuildDiskStateYaml(stateRoot, recoveryRoot string) error { var statePath, recoveryPath string diff --git a/pkg/action/build-iso.go b/pkg/action/build-iso.go index cf800fc689d..329620fa996 100644 --- a/pkg/action/build-iso.go +++ b/pkg/action/build-iso.go @@ -41,7 +41,9 @@ func grubCfgTemplate(arch string) string { menuentry "%s" --class os --unrestricted { echo Loading kernel... - linux ($root)` + constants.ISOKernelPath(arch) + ` cdroot root=live:CDLABEL=%s rd.live.dir=` + constants.ISOLoaderPath(arch) + ` rd.live.squashimg=rootfs.squashfs console=tty1 console=ttyS0 elemental.disable elemental.setup=` + constants.ISOCloudInitPath + ` + linux ($root)` + constants.ISOKernelPath(arch) + ` cdroot root=live:CDLABEL=%s rd.live.dir=` + constants.ISOLoaderPath(arch) + + ` rd.live.squashimg=rootfs.squashfs security=selinux enforcing=0 console=tty1 console=ttyS0 elemental.disable elemental.setup=` + + constants.ISOCloudInitPath + ` echo Loading initrd... initrd ($root)` + constants.ISOInitrdPath(arch) + ` } diff --git a/pkg/action/install.go b/pkg/action/install.go index 0588c4a6beb..3e7ac35b299 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -324,13 +324,6 @@ func (i *InstallAction) refineDeployment() error { //nolint:dupl return elementalError.NewFromError(err, elementalError.InstallGrub) } - // Relabel SELinux - err = elemental.ApplySelinuxLabels(i.cfg.Config, i.spec.Partitions) - if err != nil { - i.cfg.Logger.Errorf("failed setting SELinux labels: %v", err) - return elementalError.NewFromError(err, elementalError.SelinuxRelabel) - } - err = i.installChrootHook(cnst.AfterInstallChrootHook, cnst.WorkingImgDir) if err != nil { i.cfg.Logger.Errorf("failed after-install-chroot hook: %v", err) diff --git a/pkg/action/reset.go b/pkg/action/reset.go index 7eef438e150..a91f2925af4 100644 --- a/pkg/action/reset.go +++ b/pkg/action/reset.go @@ -297,13 +297,6 @@ func (r *ResetAction) refineDeployment() error { //nolint:dupl return elementalError.NewFromError(err, elementalError.InstallGrub) } - // Relabel SELinux - err = elemental.ApplySelinuxLabels(r.cfg.Config, r.spec.Partitions) - if err != nil { - r.cfg.Logger.Errorf("failed setting SELinux labels: %v", err) - return elementalError.NewFromError(err, elementalError.SelinuxRelabel) - } - err = r.resetChrootHook(constants.AfterResetChrootHook, constants.WorkingImgDir) if err != nil { r.cfg.Logger.Errorf("failed after-reset-chroot hook: %v", err) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 273428df9ee..afd5a3bdd1b 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -361,13 +361,6 @@ func (u *UpgradeAction) refineDeployment() error { //nolint:dupl } } - // Relabel SELinux - err = elemental.ApplySelinuxLabels(u.cfg.Config, u.spec.Partitions) - if err != nil { - u.cfg.Logger.Errorf("failed setting SELinux labels: %v", err) - return elementalError.NewFromError(err, elementalError.SelinuxRelabel) - } - err = u.upgradeChrootHook(constants.AfterUpgradeChrootHook, constants.WorkingImgDir) if err != nil { u.Error("Error running hook after-upgrade-chroot: %s", err) diff --git a/pkg/elemental/elemental.go b/pkg/elemental/elemental.go index fc9efd4bbf8..2251f475c51 100644 --- a/pkg/elemental/elemental.go +++ b/pkg/elemental/elemental.go @@ -363,6 +363,11 @@ func CreateImageFromTree(c types.Config, img *types.Image, rootDir string, prelo return err } + err = SelinuxRelabel(c, rootDir) + if err != nil { + c.Logger.Warnf("failed SELinux labelling at %s: %v", rootDir, err) + } + err = utils.CreateSquashFS(c.Runner, c.Logger, rootDir, img.File, c.SquashFsCompressionConfig) if err != nil { c.Logger.Errorf("failed creating squashfs image for %s: %v", img.File, err) @@ -398,6 +403,12 @@ func CreateImageFromTree(c types.Config, img *types.Image, rootDir string, prelo c.Logger.Errorf("failed creating dir structure: %v", err) return err } + + err = ApplySELinuxLabels(c, img.MountPoint, nil) + if err != nil { + c.Logger.Errorf("failed SELinux labelling at %s: %v", img.MountPoint, err) + return err + } } } return err @@ -406,7 +417,7 @@ func CreateImageFromTree(c types.Config, img *types.Image, rootDir string, prelo // CopyFileImg copies the files target as the source of this image. It also applies the img label over the copied image. func CopyFileImg(c types.Config, img *types.Image) error { if !img.Source.IsFile() { - return fmt.Errorf("Copying a file image requires an image source of file type") + return fmt.Errorf("copying a file image requires an image source of file type") } err := utils.MkdirAll(c.Fs, filepath.Dir(img.File), cnst.DirPerm) @@ -659,19 +670,20 @@ func CopyCloudConfig(c types.Config, path string, cloudInit []string) (err error } // SelinuxRelabel will relabel the system if it finds the binary and the context -func SelinuxRelabel(c types.Config, rootDir string) error { - policyFile, err := utils.FindFile(c.Fs, rootDir, filepath.Join(cnst.SELinuxTargetedPolicyPath, "policy.*")) +func SelinuxRelabel(c types.Config, rootDir string, extraPaths ...string) error { contextFile := filepath.Join(rootDir, cnst.SELinuxTargetedContextFile) contextExists, _ := utils.Exists(c.Fs, contextFile) - if err == nil && contextExists && c.Runner.CommandExists("setfiles") { + if contextExists && c.Runner.CommandExists("setfiles") { var out []byte var err error + var args []string if rootDir == "/" || rootDir == "" { - out, err = c.Runner.Run("setfiles", "-c", policyFile, "-e", "/dev", "-e", "/proc", "-e", "/sys", "-F", contextFile, "/") + args = append([]string{"-e", "/dev", "-e", "/proc", "-e", "/sys", "-i", "-F", contextFile, "/"}, extraPaths...) } else { - out, err = c.Runner.Run("setfiles", "-c", policyFile, "-F", "-r", rootDir, contextFile, rootDir) + args = append([]string{"-i", "-F", "-r", rootDir, contextFile, rootDir}, extraPaths...) } + out, err = c.Runner.Run("setfiles", args...) c.Logger.Debugf("SELinux setfiles output: %s", string(out)) return err } @@ -680,18 +692,34 @@ func SelinuxRelabel(c types.Config, rootDir string) error { return nil } -// ApplySelinuxLabels sets SELinux extended attributes to the root-tree being installed -func ApplySelinuxLabels(cfg types.Config, parts types.ElementalPartitions) error { - binds := map[string]string{} - if mnt, _ := IsMounted(cfg, parts.Persistent); mnt { - binds[parts.Persistent.MountPoint] = cnst.PersistentPath +// ApplySELinuxLabels relables with setfiles the given root in a chroot env. Additionaly after the first +// chrooted call it runs a non chrooted call to relabel any mountpoint used within the chroot. +func ApplySELinuxLabels(c types.Config, rootDir string, bind map[string]string) (err error) { + var out []byte + extraPaths := []string{} + + for _, v := range bind { + extraPaths = append(extraPaths, v) + } + + err = utils.ChrootedCallback(&c, rootDir, bind, func() error { return SelinuxRelabel(c, "/", extraPaths...) }) + if err != nil { + return err } - if mnt, _ := IsMounted(cfg, parts.OEM); mnt { - binds[parts.OEM.MountPoint] = cnst.OEMPath + + contextsFile := filepath.Join(rootDir, cnst.SELinuxTargetedContextFile) + existsCon, _ := utils.Exists(c.Fs, contextsFile) + + if existsCon && c.Runner.CommandExists("setfiles") { + args := []string{"-r", rootDir, "-i", "-F", contextsFile} + for _, path := range append([]string{"/dev", "/proc", "/sys"}, extraPaths...) { + args = append(args, filepath.Join(rootDir, path)) + } + out, err = c.Runner.Run("setfiles", args...) + c.Logger.Debugf("SELinux setfiles output: %s", string(out)) } - return utils.ChrootedCallback( - &cfg, cnst.WorkingImgDir, binds, func() error { return SelinuxRelabel(cfg, "/") }, - ) + + return err } // CheckActiveDeployment returns true if at least one of the mode sentinel files is found diff --git a/pkg/elemental/elemental_test.go b/pkg/elemental/elemental_test.go index a669df4a138..8cfead20866 100644 --- a/pkg/elemental/elemental_test.go +++ b/pkg/elemental/elemental_test.go @@ -885,8 +885,8 @@ var _ = Describe("Elemental", Label("elemental"), func() { Expect(err).ShouldNot(HaveOccurred()) relabelCmd = []string{ - "setfiles", "-c", policyFile, "-e", "/dev", "-e", "/proc", "-e", "/sys", - "-F", constants.SELinuxTargetedContextFile, "/", + "setfiles", "-e", "/dev", "-e", "/proc", "-e", "/sys", + "-i", "-F", constants.SELinuxTargetedContextFile, "/", } }) It("does nothing if the context file is not found", func() { @@ -929,7 +929,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { Expect(err).ShouldNot(HaveOccurred()) relabelCmd = []string{ - "setfiles", "-c", policyFile, "-F", "-r", "/root", contextFile, "/root", + "setfiles", "-i", "-F", "-r", "/root", contextFile, "/root", } Expect(elemental.SelinuxRelabel(*config, "/root")).To(BeNil()) diff --git a/pkg/features/embedded/cloud-config-defaults/system/oem/10_selinux.yaml b/pkg/features/embedded/cloud-config-defaults/system/oem/10_selinux.yaml index 224afd9958a..08433234566 100644 --- a/pkg/features/embedded/cloud-config-defaults/system/oem/10_selinux.yaml +++ b/pkg/features/embedded/cloud-config-defaults/system/oem/10_selinux.yaml @@ -8,10 +8,18 @@ name: "SELinux" stages: initramfs: - - name: "Relabelling" + - name: "SELinux labels for targeted policy" commands: - | - if grep -q "selinux=1" /proc/cmdline; then - load_policy -i - restorecon -R -i -v /etc /root /opt /srv /var /home /usr/local /oem + contexts="/etc/selinux/targeted/contexts/files/file_contexts" + if grep -qw selinux /sys/kernel/security/lsm && [ -e "${contexts}" ]; then + # Some extended attributes are lost on copy-up bsc#1210690. + # Workaround visit children first, then parents + mkdir -p /run/systemd/relabel-extra.d/ + for path in /etc /srv /var /oem /home /root /opt; do + if [ -d "${path}" ]; then + find ${path} -depth -exec /sbin/setfiles -i -F -v "${contexts}" \{\} + + echo "${path}" >> /run/systemd/relabel-extra.d/layout.relabel + fi + done fi diff --git a/pkg/features/embedded/grub-default-bootargs/etc/elemental/bootargs.cfg b/pkg/features/embedded/grub-default-bootargs/etc/elemental/bootargs.cfg index 12c8593d1f7..b7fe95e2972 100644 --- a/pkg/features/embedded/grub-default-bootargs/etc/elemental/bootargs.cfg +++ b/pkg/features/embedded/grub-default-bootargs/etc/elemental/bootargs.cfg @@ -15,12 +15,12 @@ if [ -n "${img}" ]; then fi if [ "${mode}" == "recovery" ]; then - set kernelcmd="console=tty1 console=ttyS0 root=LABEL=${recovery_label} ${img_arg} elemental.mode=${mode} elemental.oemlabel=${oem_label} security=selinux selinux=0 rd.neednet=1" + set kernelcmd="console=tty1 console=ttyS0 root=LABEL=${recovery_label} ${img_arg} elemental.mode=${mode} elemental.oemlabel=${oem_label} security=selinux enforcing=0 rd.neednet=1" else if [ "${snapshotter}" == "btrfs" ]; then set snap_arg="elemental.snapshotter=btrfs" fi - set kernelcmd="console=tty1 console=ttyS0 root=LABEL=${state_label} ${img_arg} ${snap_arg} elemental.mode=${mode} elemental.oemlabel=${oem_label} panic=5 security=selinux selinux=0 rd.neednet=1 fsck.mode=force fsck.repair=yes" + set kernelcmd="console=tty1 console=ttyS0 root=LABEL=${state_label} ${img_arg} ${snap_arg} elemental.mode=${mode} elemental.oemlabel=${oem_label} panic=5 security=selinux rd.neednet=1 fsck.mode=force fsck.repair=yes" fi set kernel=/boot/vmlinuz diff --git a/pkg/snapshotter/btrfs.go b/pkg/snapshotter/btrfs.go index 76848d64b1f..14d661a4278 100644 --- a/pkg/snapshotter/btrfs.go +++ b/pkg/snapshotter/btrfs.go @@ -320,6 +320,13 @@ func (b *Btrfs) CloseTransaction(snapshot *types.Snapshot) (err error) { } } + extraBind := map[string]string{filepath.Join(b.rootDir, snapshotsPath): filepath.Join("/", snapshotsPath)} + err = elemental.ApplySELinuxLabels(b.cfg, snapshot.Path, extraBind) + if err != nil { + b.cfg.Logger.Errorf("failed relabelling snapshot path: %s", snapshot.Path) + return err + } + cmdOut, err = b.cfg.Runner.Run("btrfs", "property", "set", snapshot.Path, "ro", "true") if err != nil { b.cfg.Logger.Errorf("failed setting read only property to snapshot %d: %s", snapshot.ID, string(cmdOut)) @@ -676,20 +683,24 @@ func (b *Btrfs) remountStatePartition(state *types.Partition) error { return err } - b.cfg.Logger.Debugf("Mount snapshots subvolume in active snapshot") if b.activeSnapshotID > 0 { - snapperRoot := filepath.Join(state.MountPoint, fmt.Sprintf(snapshotPathTmpl, b.activeSnapshotID)) - mountpoint := filepath.Join(snapperRoot, snapshotsPath) - subvol := fmt.Sprintf("subvol=%s", filepath.Join(rootSubvol, snapshotsPath)) - err = b.cfg.Mounter.Mount(state.Path, mountpoint, "btrfs", []string{"rw", subvol}) - if err != nil { - b.cfg.Logger.Errorf("failed mounting subvolume %s at %s", subvol, mountpoint) - return err - } - b.snapshotsUmount = func() error { return b.cfg.Mounter.Unmount(mountpoint) } - b.snapperArgs = []string{"--no-dbus", "--root", snapperRoot} + err = b.mountSnapshotsSubvolumeInSnapshot(state.MountPoint, state.Path, b.activeSnapshotID) + b.snapperArgs = []string{"--no-dbus", "--root", filepath.Join(state.MountPoint, fmt.Sprintf(snapshotPathTmpl, b.activeSnapshotID))} } b.rootDir = state.MountPoint + return err +} + +func (b *Btrfs) mountSnapshotsSubvolumeInSnapshot(root, device string, snapshotID int) error { + b.cfg.Logger.Debugf("Mount snapshots subvolume in active snapshot %d", snapshotID) + mountpoint := filepath.Join(filepath.Join(root, fmt.Sprintf(snapshotPathTmpl, snapshotID)), snapshotsPath) + subvol := fmt.Sprintf("subvol=%s", filepath.Join(rootSubvol, snapshotsPath)) + err := b.cfg.Mounter.Mount(device, mountpoint, "btrfs", []string{"rw", subvol}) + if err != nil { + b.cfg.Logger.Errorf("failed mounting subvolume %s at %s", subvol, mountpoint) + return err + } + b.snapshotsUmount = func() error { return b.cfg.Mounter.Unmount(mountpoint) } return nil } diff --git a/pkg/snapshotter/btrfs_test.go b/pkg/snapshotter/btrfs_test.go index 6308a0b94f5..12eae5703d2 100644 --- a/pkg/snapshotter/btrfs_test.go +++ b/pkg/snapshotter/btrfs_test.go @@ -46,6 +46,7 @@ var _ = Describe("Btrfs", Label("snapshotter", " btrfs"), func() { var snapCfg types.SnapshotterConfig var rootDir, efiDir string var statePart *types.Partition + var syscall *mocks.FakeSyscall BeforeEach(func() { rootDir = "/some/root" @@ -57,6 +58,7 @@ var _ = Describe("Btrfs", Label("snapshotter", " btrfs"), func() { efiDir = constants.EfiDir runner = mocks.NewFakeRunner() mounter = mocks.NewFakeMounter() + syscall = &mocks.FakeSyscall{} bootloader = &mocks.FakeBootloader{} memLog = bytes.NewBuffer(nil) logger = types.NewBufferLogger(memLog) @@ -71,6 +73,7 @@ var _ = Describe("Btrfs", Label("snapshotter", " btrfs"), func() { conf.WithRunner(runner), conf.WithLogger(logger), conf.WithMounter(mounter), + conf.WithSyscall(syscall), conf.WithPlatform("linux/amd64"), ) snapCfg = types.SnapshotterConfig{ diff --git a/pkg/snapshotter/loopdevice_test.go b/pkg/snapshotter/loopdevice_test.go index 6afa630244e..7620958126b 100644 --- a/pkg/snapshotter/loopdevice_test.go +++ b/pkg/snapshotter/loopdevice_test.go @@ -44,6 +44,7 @@ var _ = Describe("LoopDevice", Label("snapshotter", "loopdevice"), func() { var snapCfg types.SnapshotterConfig var rootDir, efiDir string var statePart *types.Partition + var syscall *mocks.FakeSyscall BeforeEach(func() { rootDir = "/some/root" @@ -55,6 +56,7 @@ var _ = Describe("LoopDevice", Label("snapshotter", "loopdevice"), func() { efiDir = constants.EfiDir runner = mocks.NewFakeRunner() mounter = mocks.NewFakeMounter() + syscall = &mocks.FakeSyscall{} bootloader = &mocks.FakeBootloader{} memLog = bytes.NewBuffer(nil) logger = types.NewBufferLogger(memLog) @@ -69,6 +71,7 @@ var _ = Describe("LoopDevice", Label("snapshotter", "loopdevice"), func() { conf.WithRunner(runner), conf.WithLogger(logger), conf.WithMounter(mounter), + conf.WithSyscall(syscall), conf.WithPlatform("linux/amd64"), ) snapCfg = types.NewLoopDevice() diff --git a/pkg/utils/chroot.go b/pkg/utils/chroot.go index 222c4d7f249..0f1c11e1e71 100644 --- a/pkg/utils/chroot.go +++ b/pkg/utils/chroot.go @@ -50,6 +50,9 @@ func NewChroot(path string, config *types.Config) *Chroot { // ChrootedCallback runs the given callback in a chroot environment func ChrootedCallback(cfg *types.Config, path string, bindMounts map[string]string, callback func() error) error { chroot := NewChroot(path, cfg) + if bindMounts == nil { + bindMounts = map[string]string{} + } chroot.SetExtraMounts(bindMounts) return chroot.RunCallback(callback) } diff --git a/pkg/utils/common.go b/pkg/utils/common.go index 6e2ef23c0c2..509cb8c4847 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -156,7 +156,7 @@ func CreateDirStructure(fs types.FS, target string) error { // SyncData rsync's source folder contents to a target folder content, // both are expected to exist before hand. func SyncData(log types.Logger, runner types.Runner, fs types.FS, source string, target string, excludes ...string) error { - flags := []string{"--progress", "--partial", "--human-readable", "--archive", "--xattrs", "--acls"} + flags := []string{"--progress", "--partial", "--human-readable", "--archive", "--xattrs", "--acls", "--filter=-x security.selinux"} for _, e := range excludes { flags = append(flags, fmt.Sprintf("--exclude=%s", e)) } @@ -167,7 +167,7 @@ func SyncData(log types.Logger, runner types.Runner, fs types.FS, source string, // MirrorData rsync's source folder contents to a target folder content, in contrast, to SyncData this // method includes the --delete flag which forces the deletion of files in target that are missing in source. func MirrorData(log types.Logger, runner types.Runner, fs types.FS, source string, target string, excludes ...string) error { - flags := []string{"--progress", "--partial", "--human-readable", "--archive", "--xattrs", "--acls", "--delete"} + flags := []string{"--progress", "--partial", "--human-readable", "--archive", "--xattrs", "--acls", "--delete", "--filter=-x security.selinux"} for _, e := range excludes { flags = append(flags, fmt.Sprintf("--exclude=%s", e)) } diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index eae07b49777..fc797ba394d 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -97,11 +97,11 @@ var _ = Describe("Utils", Label("utils"), func() { }) Describe("ChrootedCallback method", func() { It("runs a callback in a chroot", func() { - err := utils.ChrootedCallback(config, "/somepath", map[string]string{}, func() error { + err := utils.ChrootedCallback(config, "/somepath", nil, func() error { return nil }) Expect(err).ShouldNot(HaveOccurred()) - err = utils.ChrootedCallback(config, "/somepath", map[string]string{}, func() error { + err = utils.ChrootedCallback(config, "/somepath", nil, func() error { return fmt.Errorf("callback error") }) Expect(err).Should(HaveOccurred())