From 685fd0338e99e6c193fc90043a0a1a58c0ee6380 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 16 Oct 2025 11:15:27 -0600 Subject: [PATCH] Use a native library for SELinux in wwclient This removes the dependency on shelling out to commands in the image. Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 1 + internal/app/wwclient/root.go | 65 +++++++++++++++-------------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2314ffa7..ce2989cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Manage SELinux context of TFTP directory. #1997 - Dynamically write `$tftpdir/warewulf/grub.cfg` to the configured value from `warewulf.conf` - Absolute paths specified with `{{ file }}` in an overlay now write to that absolute path. +- Use opencontainers/selinux to manage SELinux in wwclient. ### Fixed diff --git a/internal/app/wwclient/root.go b/internal/app/wwclient/root.go index 464927c5..358c0b6b 100644 --- a/internal/app/wwclient/root.go +++ b/internal/app/wwclient/root.go @@ -17,10 +17,12 @@ import ( "github.com/coreos/go-systemd/daemon" "github.com/google/uuid" + "github.com/opencontainers/selinux/go-selinux" "github.com/spf13/cobra" "github.com/talos-systems/go-smbios/smbios" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/pidfile" + "github.com/warewulf/warewulf/internal/pkg/version" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -82,6 +84,8 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { } defer cleanUp() + wwlog.Debug("Version: %s", version.GetVersion()) + target := "/" if os.Args[0] == path.Join(conf.Paths.WWClientdir, "wwclient") { wwlog.Warn("updating live file system: cancel now if this is in error") @@ -537,56 +541,43 @@ func cleanUp() { } } -// isSELinuxEnabled checks if SELinux is present and enabled on the system -func isSELinuxEnabled() bool { - // Check if SELinux filesystem is mounted - if _, err := os.Stat("/sys/fs/selinux"); os.IsNotExist(err) { - return false - } - - // Check if SELinux is enabled (enforcing or permissive) - _, err := os.ReadFile("/sys/fs/selinux/enforce") - return err == nil -} - // setSELinuxContextForDestination sets the SELinux context on a temporary file or symlink // to match what the destination path should have func setSELinuxContextForDestination(tempPath, destPath string) error { - if !isSELinuxEnabled() { + if !selinux.GetEnabled() { + wwlog.Debug("SELinux not enabled, skipping context setting for %s", tempPath) return nil } - wwlog.Debug("setting SELinux context for temp file %s based on destination %s", tempPath, destPath) - - // Use matchpathcon to get the expected context for the destination - cmd := exec.Command("matchpathcon", "-n", destPath) - output, err := cmd.Output() - if err != nil { - // If matchpathcon fails, fall back to restorecon on the temp file - wwlog.Debug("matchpathcon failed for %s, using restorecon: %s", destPath, err) - cmd = exec.Command("restorecon", "-F", tempPath) - if err := cmd.Run(); err != nil { - wwlog.Warn("failed to restore SELinux context for temp file %s: %s", tempPath, err) + // Try to get the existing destination's context to preserve it. + // + // We prefer the existing context if the file already exists, because the + // context is not defined by the overlay itself. + refPath := destPath + expectedContext, err := selinux.LfileLabel(refPath) + if err != nil || expectedContext == "" { + // If destination doesn't exist, compute what context it should have based on parent directory + wwlog.Debug("unable to get context from destination %s", refPath) + refPath = filepath.Dir(destPath) + parentContext, err := selinux.FileLabel(refPath) + if err != nil || parentContext == "" { + wwlog.Debug("unable to get context from parent %s", refPath) return err } - return nil - } - expectedContext := strings.TrimSpace(string(output)) - if expectedContext == "" { - wwlog.Debug("empty context from matchpathcon for %s, using restorecon", destPath) - cmd = exec.Command("restorecon", "-F", tempPath) - if err := cmd.Run(); err != nil { - wwlog.Warn("failed to restore SELinux context for temp file %s: %s", tempPath, err) + // Compute what the kernel would assign for a file created in this + // parent directory + expectedContext, err = selinux.ComputeCreateContext(parentContext, parentContext, "file") + if err != nil || expectedContext == "" { + wwlog.Debug("could not compute context from parent %s", refPath) return err } - return nil } - // Set the context on the temp file/symlink - cmd = exec.Command("chcon", "-h", expectedContext, tempPath) - if err := cmd.Run(); err != nil { - wwlog.Warn("failed to set SELinux context %s for temp file %s: %s", expectedContext, tempPath, err) + // Use LsetFileLabel for symlinks (it's safe for regular files too) + wwlog.Debug("setting context %s on temp file %s from %s", expectedContext, tempPath, refPath) + if err := selinux.LsetFileLabel(tempPath, expectedContext); err != nil { + wwlog.Warn("failed to set context %s for temp file %s: %s", expectedContext, tempPath, err) return err }