diff --git a/internal/pkg/extensions/compress.go b/internal/pkg/extensions/compress.go index 2a2418ca0..8bf7e80d2 100644 --- a/internal/pkg/extensions/compress.go +++ b/internal/pkg/extensions/compress.go @@ -12,6 +12,8 @@ import ( "os" "os/exec" "path/filepath" + "slices" + "strings" "github.com/siderolabs/talos/pkg/machinery/imager/quirks" ) @@ -47,7 +49,7 @@ func initramfsPaths(quirks quirks.Quirks) []string { // // Components which should be placed to the initramfs are moved to the initramfsPath. // Ucode components are moved into a separate designated location. -func (ext *Extension) Compress(ctx context.Context, squashPath, initramfsPath string, quirks quirks.Quirks) (string, error) { +func (ext *Extension) Compress(ctx context.Context, squashPath, initramfsPath string, quirks quirks.Quirks, xattrsMap map[string]string) (string, error) { if err := ext.handleUcode(initramfsPath, quirks); err != nil { return "", err } @@ -70,12 +72,62 @@ func (ext *Extension) Compress(ctx context.Context, squashPath, initramfsPath st compressArgs = []string{"-comp", "xz", "-Xdict-size", "100%"} } - cmd := exec.CommandContext(ctx, "mksquashfs", append([]string{ext.RootfsPath(), squashPath, "-all-root", "-noappend", "-no-progress"}, compressArgs...)...) + pseudoFlags, err := ext.xattrPseudoFlags(xattrsMap) + if err != nil { + return "", err + } + + cmd := exec.CommandContext(ctx, "mksquashfs", + slices.Concat( + []string{ + ext.RootfsPath(), + squashPath, + "-all-root", + "-noappend", + "-no-progress", + }, + compressArgs, + pseudoFlags, + )...) cmd.Stderr = os.Stderr return squashPath, cmd.Run() } +// xattrPseudoFlags returns a list of pseudo-flag strings for the xattrs of the extension. +// +// These pseudo-flags are used to indicate the presence of specific SELinux xattrs on files within the extension. +// The mksquashfs tool will use that to mark files with xattrs instead of reading it from the filesystem. +func (ext *Extension) xattrPseudoFlags(xattrsMap map[string]string) ([]string, error) { + if xattrsMap == nil { + return nil, nil + } + + flags := []string{"-xattrs-exclude", ".*"} // exclude all xattrs by default + + for path, xattrValue := range xattrsMap { + if strings.HasPrefix(path, ext.RootfsPath()) { + // check if the file exists still (it might have been moved to the initramfs) + if _, err := os.Lstat(path); os.IsNotExist(err) { + continue + } + + relativePath, err := filepath.Rel(ext.RootfsPath(), path) + if err != nil { + return nil, err + } + + if relativePath == "." { + relativePath = "/" + } + + flags = append(flags, "-p", fmt.Sprintf("%s x security.selinux=%s", relativePath, xattrValue)) + } + } + + return flags, nil +} + func appendBlob(dst io.Writer, srcPath string) error { src, err := os.Open(srcPath) if err != nil { diff --git a/internal/pkg/extensions/extensions_test.go b/internal/pkg/extensions/extensions_test.go index fa1a0385c..bfb81ae85 100644 --- a/internal/pkg/extensions/extensions_test.go +++ b/internal/pkg/extensions/extensions_test.go @@ -30,7 +30,7 @@ func TestCompress(t *testing.T) { ext := exts[0] squashDest, initramfsDest := t.TempDir(), t.TempDir() - squashFile, err := ext.Compress(t.Context(), squashDest, initramfsDest, quirks.New("")) + squashFile, err := ext.Compress(t.Context(), squashDest, initramfsDest, quirks.New(""), nil) assert.NoError(t, err) assert.FileExists(t, squashFile) diff --git a/pkg/archiver/archiver.go b/pkg/archiver/archiver.go index de7b9ebe4..d6a26f1b9 100644 --- a/pkg/archiver/archiver.go +++ b/pkg/archiver/archiver.go @@ -29,21 +29,3 @@ func TarGz(ctx context.Context, rootPath string, output io.Writer, walkerOptions return zw.Close() } - -// UntarGz extracts .tar.gz archive to the rootPath. -func UntarGz(ctx context.Context, input io.Reader, rootPath string) error { - zr, err := gzip.NewReader(input) - if err != nil { - return err - } - - //nolint:errcheck - defer zr.Close() - - err = Untar(ctx, zr, rootPath) - if err != nil { - return err - } - - return zr.Close() -} diff --git a/pkg/archiver/untar.go b/pkg/archiver/untar.go index 30a63c27f..84087ee76 100644 --- a/pkg/archiver/untar.go +++ b/pkg/archiver/untar.go @@ -13,15 +13,19 @@ import ( "os" "path/filepath" - "github.com/pkg/xattr" - + "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/safepath" ) // Untar extracts .tar archive from r into filesystem under rootPath. // -//nolint:gocyclo -func Untar(ctx context.Context, r io.Reader, rootPath string) error { +// If xattrsMap is not nil, it will be filled with paths and their corresponding +// SELinux xattr values instead of setting the xattrs on the filesystem. +// +// If xattrsMap is nil, the function will ignore SELinux xattr values. +// +//nolint:gocyclo,cyclop +func Untar(ctx context.Context, r io.Reader, rootPath string, xattrsMap map[string]string) error { tr := tar.NewReader(r) for { @@ -87,10 +91,8 @@ func Untar(ctx context.Context, r io.Reader, rootPath string) error { } } - if hdr.PAXRecords["SCHILY.xattr.security.selinux"] != "" { - if err = xattr.LSet(path, "security.selinux", []byte(hdr.PAXRecords["SCHILY.xattr.security.selinux"])); err != nil { - return fmt.Errorf("error setting selinux xattr for %q: %w", path, err) - } + if hdr.PAXRecords[constants.TarPaxHeaderSELinux] != "" && xattrsMap != nil { + xattrsMap[path] = hdr.PAXRecords[constants.TarPaxHeaderSELinux] } } diff --git a/pkg/imager/embed.go b/pkg/imager/embed.go index e16a1c398..14bfa49d0 100644 --- a/pkg/imager/embed.go +++ b/pkg/imager/embed.go @@ -112,10 +112,10 @@ func BuildEmbeddedConfigExtension(machineConfig []byte) (io.Reader, error) { if err = tw.WriteHeader(&tar.Header{ Name: filepath.Join("rootfs", constants.EmbeddedConfigDirectory, constants.ConfigFilename), Typeflag: tar.TypeReg, - Mode: 0o000, + Mode: 0o400, Size: int64(len(machineConfig)), PAXRecords: map[string]string{ - "SCHILY.xattr.security.selinux": constants.StateSelinuxLabel, + constants.TarPaxHeaderSELinux: constants.StateSelinuxLabel, }, }); err != nil { return nil, fmt.Errorf("failed to write embedded header: %w", err) diff --git a/pkg/imager/extensions/extensions.go b/pkg/imager/extensions/extensions.go index 4915c81de..c70d4218e 100644 --- a/pkg/imager/extensions/extensions.go +++ b/pkg/imager/extensions/extensions.go @@ -31,6 +31,8 @@ type Builder struct { Printf func(format string, v ...any) // Quirks for the Talos version being used. Quirks quirks.Quirks + // XAttrsMap is used to store paths and their corresponding SELinux xattr values during extraction of extensions. + XAttrsMap map[string]string } // Build rebuilds the initramfs.xz with extensions. @@ -122,7 +124,7 @@ func (builder *Builder) compressExtensions(ctx context.Context, extensions []*ex builder.Printf("compressing system extensions") for _, ext := range extensions { - path, err := ext.Compress(ctx, tempDir, tempDir, builder.Quirks) + path, err := ext.Compress(ctx, tempDir, tempDir, builder.Quirks, builder.XAttrsMap) if err != nil { return nil, fmt.Errorf("error compressing extension %q: %w", ext.Manifest.Metadata.Name, err) } diff --git a/pkg/imager/imager.go b/pkg/imager/imager.go index 60502a44f..c1cdddf58 100644 --- a/pkg/imager/imager.go +++ b/pkg/imager/imager.go @@ -47,12 +47,16 @@ type Imager struct { sdBootPath string ukiPath string + + // xattrsMap is used to store paths and their corresponding SELinux xattr values during extraction of extensions. + xattrsMap map[string]string } // New creates a new Imager. func New(prof profile.Profile) (*Imager, error) { return &Imager{ - prof: prof, + prof: prof, + xattrsMap: map[string]string{}, }, nil } @@ -198,7 +202,7 @@ func (i *Imager) handleOverlay(ctx context.Context, report *reporter.Reporter) e return fmt.Errorf("failed to create overlay directory: %w", err) } - if err := i.prof.Overlay.Image.Extract(ctx, tempOverlayPath, runtime.GOARCH, progressPrintf(report, reporter.Update{Message: "pulling overlay...", Status: reporter.StatusRunning})); err != nil { + if err := i.prof.Overlay.Image.Extract(ctx, tempOverlayPath, runtime.GOARCH, progressPrintf(report, reporter.Update{Message: "pulling overlay...", Status: reporter.StatusRunning}), nil); err != nil { return err } @@ -316,7 +320,7 @@ func (i *Imager) buildInitramfs(ctx context.Context, report *reporter.Reporter) return fmt.Errorf("failed to create extension directory: %w", err) } - if err := ext.Extract(ctx, extensionDir, i.prof.Arch, printf); err != nil { + if err := ext.Extract(ctx, extensionDir, i.prof.Arch, printf, i.xattrsMap); err != nil { return err } } @@ -328,6 +332,7 @@ func (i *Imager) buildInitramfs(ctx context.Context, report *reporter.Reporter) ExtensionTreePath: extensionsCheckoutDir, Printf: printf, Quirks: quirks.New(i.prof.Version), + XAttrsMap: i.xattrsMap, } if err := builder.Build(ctx); err != nil { diff --git a/pkg/imager/out.go b/pkg/imager/out.go index a796e4f8e..95ada3059 100644 --- a/pkg/imager/out.go +++ b/pkg/imager/out.go @@ -97,7 +97,7 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor return err } - if err := i.prof.Input.ImageCache.Extract(ctx, filepath.Join(scratchSpace, "imagecache"), i.prof.Arch, printf); err != nil { + if err := i.prof.Input.ImageCache.Extract(ctx, filepath.Join(scratchSpace, "imagecache"), i.prof.Arch, printf, nil); err != nil { return err } } @@ -358,7 +358,7 @@ func (i *Imager) buildImage(ctx context.Context, path string, printf func(string return err } - if err := i.prof.Input.ImageCache.Extract(ctx, imageCacheDir, i.prof.Arch, printf); err != nil { + if err := i.prof.Input.ImageCache.Extract(ctx, imageCacheDir, i.prof.Arch, printf, nil); err != nil { return err } @@ -540,6 +540,7 @@ func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter tempOverlayPath, i.prof.Arch, progressPrintf(report, reporter.Update{Message: "pulling overlay for installer...", Status: reporter.StatusRunning}), + nil, ); err != nil { return err } diff --git a/pkg/imager/profile/input.go b/pkg/imager/profile/input.go index 8b4a2cbeb..5bbb44ed7 100644 --- a/pkg/imager/profile/input.go +++ b/pkg/imager/profile/input.go @@ -320,7 +320,12 @@ func (c *ContainerAsset) pullFromOCI(arch string) (v1.Image, error) { } // Extract the container asset to the path. -func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string, printf func(string, ...any)) error { +func (c *ContainerAsset) Extract( + ctx context.Context, + destination, arch string, + printf func(string, ...any), + xattrsMap map[string]string, +) error { if c.TarballPath != "" { in, err := os.Open(c.TarballPath) if err != nil { @@ -331,7 +336,7 @@ func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string, printf("extracting %s...", c.TarballPath) - return archiver.Untar(ctx, in, destination) + return archiver.Untar(ctx, in, destination, xattrsMap) } img, err := c.Pull(ctx, arch, printf) @@ -356,7 +361,7 @@ func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string, }) eg.Go(func() error { - if untarErr := archiver.Untar(ctx, r, destination); untarErr != nil { + if untarErr := archiver.Untar(ctx, r, destination, xattrsMap); untarErr != nil { r.CloseWithError(untarErr) return untarErr diff --git a/pkg/machinery/constants/constants.go b/pkg/machinery/constants/constants.go index e1efbd52b..b46fdd873 100644 --- a/pkg/machinery/constants/constants.go +++ b/pkg/machinery/constants/constants.go @@ -1329,6 +1329,9 @@ const ( // ImageLabelVerified is the label key for the verified image label. ImageLabelVerified = "talos.dev/verified" + + // TarPaxHeaderSELinux is the name of the PAX header for storing SELinux labels. + TarPaxHeaderSELinux = "SCHILY.xattr.security.selinux" ) // names of variable that can be substituted in the talos.config kernel parameter.