Skip to content

Commit

Permalink
feat: add SELinux labels to volumes
Browse files Browse the repository at this point in the history
Label mounted filesystems like ephemeral, overlay mounts, as well as data directories (going to become volumes later).

Signed-off-by: Dmitry Sharshakov <dmitry.sharshakov@siderolabs.com>
  • Loading branch information
dsseng committed Nov 21, 2024
1 parent 61b9129 commit 1a8cc5f
Show file tree
Hide file tree
Showing 27 changed files with 420 additions and 155 deletions.
1 change: 1 addition & 0 deletions api/resource/definitions/block/block.proto
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ message LocatorSpec {
// MountSpec is the spec for volume mount.
message MountSpec {
string target_path = 1;
string selinux_label = 2;
}

// PartitionSpec is the spec for volume partitioning.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func (ctrl *UserDiskConfigController) Run(ctx context.Context, r controller.Runt
Match: partitionIdxMatch(resolvedDevicePath, idx+1),
}

// TODO: label user disks
vc.TypedSpec().Mount = block.MountSpec{
TargetPath: part.MountPoint(),
}
Expand Down
9 changes: 6 additions & 3 deletions internal/app/machined/pkg/controllers/block/volume_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ func (ctrl *VolumeConfigController) manageEphemeral(config cfg.Config) func(vc *
}

vc.TypedSpec().Mount = block.MountSpec{
TargetPath: constants.EphemeralMountPoint,
TargetPath: constants.EphemeralMountPoint,
SelinuxLabel: constants.EphemeralSelinuxLabel,
}

vc.TypedSpec().Locator = block.LocatorSpec{
Expand All @@ -254,7 +255,8 @@ func (ctrl *VolumeConfigController) manageStateConfigPresent(config cfg.Config)
return func(vc *block.VolumeConfig) error {
vc.TypedSpec().Type = block.VolumeTypePartition
vc.TypedSpec().Mount = block.MountSpec{
TargetPath: constants.StateMountPoint,
TargetPath: constants.StateMountPoint,
SelinuxLabel: constants.StateSelinuxLabel,
}

vc.TypedSpec().Provisioning = block.ProvisioningSpec{
Expand Down Expand Up @@ -293,7 +295,8 @@ func (ctrl *VolumeConfigController) manageStateNoConfig(encryptionMeta *runtime.
return func(vc *block.VolumeConfig) error {
vc.TypedSpec().Type = block.VolumeTypePartition
vc.TypedSpec().Mount = block.MountSpec{
TargetPath: constants.StateMountPoint,
TargetPath: constants.StateMountPoint,
SelinuxLabel: constants.StateSelinuxLabel,
}

match := labelVolumeMatchAndNonEmpty(constants.StatePartitionLabel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func StartDashboard(_ runtime.Sequence, _ any) (runtime.TaskExecutionFunc, strin
// StartUdevd represents the task to start udevd.
func StartUdevd(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
mp := mountv2.NewSystemOverlay([]string{constants.UdevDir}, constants.UdevDir, mountv2.WithShared(), mountv2.WithFlags(unix.MS_I_VERSION))
mp := mountv2.NewSystemOverlay([]string{constants.UdevDir}, constants.UdevDir, mountv2.WithShared(), mountv2.WithFlags(unix.MS_I_VERSION), mountv2.WithSelinuxLabel(constants.UdevRulesLabel))

if _, err = mp.Mount(); err != nil {
return err
Expand Down Expand Up @@ -531,9 +531,10 @@ func SetupVarDirectory(runtime.Sequence, any) (runtime.TaskExecutionFunc, string
}

for _, dir := range []struct {
Path string
Mode os.FileMode
UID, GID int
Path string
Mode os.FileMode
UID, GID int
SELinuxLabel string
}{
{
Path: "/var/log",
Expand All @@ -552,8 +553,14 @@ func SetupVarDirectory(runtime.Sequence, any) (runtime.TaskExecutionFunc, string
Mode: 0o755,
},
{
Path: "/var/lib/kubelet",
Mode: 0o700,
Path: "/var/lib/containerd",
Mode: 0o000,
SELinuxLabel: "system_u:object_r:containerd_state_t:s0",
},
{
Path: "/var/lib/kubelet",
Mode: 0o700,
SELinuxLabel: "system_u:object_r:kubelet_state_t:s0",
},
{
Path: "/var/run/lock",
Expand All @@ -578,6 +585,10 @@ func SetupVarDirectory(runtime.Sequence, any) (runtime.TaskExecutionFunc, string
return err
}

if err := selinux.SetLabel(dir.Path, dir.SELinuxLabel); err != nil {
return err
}

if dir.UID != 0 || dir.GID != 0 {
if err := os.Chown(dir.Path, dir.UID, dir.GID); err != nil {
return err
Expand Down Expand Up @@ -661,6 +672,7 @@ func MountUserDisks(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) {
volumeStatus.TypedSpec().MountLocation,
volumeConfig.TypedSpec().Mount.TargetPath,
volumeStatus.TypedSpec().Filesystem.String(),
mountv2.WithSelinuxLabel(volumeConfig.TypedSpec().Mount.SelinuxLabel),
))
}

Expand Down
16 changes: 16 additions & 0 deletions internal/app/machined/pkg/startup/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/pkg/environment"
"github.com/siderolabs/talos/internal/pkg/mount/v2"
"github.com/siderolabs/talos/internal/pkg/selinux"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/block"
)
Expand All @@ -34,6 +35,21 @@ func SetupSystemDirectories(ctx context.Context, log *zap.Logger, rt runtime.Run
if err := os.MkdirAll(path, 0o700); err != nil {
return fmt.Errorf("setupSystemDirectories: %w", err)
}

var label string

switch path {
case constants.SystemEtcPath:
label = constants.SystemEtcSelinuxLabel
case constants.SystemVarPath:
label = constants.SystemVarSelinuxLabel
default: // /system/state is another mount
label = ""
}

if err := selinux.SetLabel(path, label); err != nil {
return err
}
}

for _, path := range []string{constants.SystemRunPath} {
Expand Down
6 changes: 6 additions & 0 deletions internal/app/machined/pkg/system/services/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/siderolabs/talos/internal/pkg/containers/image"
"github.com/siderolabs/talos/internal/pkg/environment"
"github.com/siderolabs/talos/internal/pkg/etcd"
"github.com/siderolabs/talos/internal/pkg/selinux"
"github.com/siderolabs/talos/pkg/argsbuilder"
"github.com/siderolabs/talos/pkg/conditions"
"github.com/siderolabs/talos/pkg/filetree"
Expand Down Expand Up @@ -93,6 +94,11 @@ func (e *Etcd) PreFunc(ctx context.Context, r runtime.Runtime) error {
return err
}

// Relabel in case of upgrade from older version or SELinux being disabled and then enabled.
if err := selinux.SetLabel(constants.EtcdDataPath, constants.EtcdDataSELinuxLabel); err != nil {
return err
}

// Make sure etcd user can access files in the data directory.
if err := filetree.ChownRecursive(constants.EtcdDataPath, constants.EtcdUserID, constants.EtcdUserID); err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions internal/app/machined/pkg/system/services/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func (svc *Extension) PreFunc(ctx context.Context, r runtime.Runtime) error {
// re-mount service rootfs as overlay rw mount to allow containerd to mount there /dev, /proc, etc.
rootfsPath := filepath.Join(constants.ExtensionServiceRootfsPath, svc.Spec.Name)

// TODO: label system extensions
overlay := mount.NewSystemOverlay(
[]string{rootfsPath},
rootfsPath,
Expand Down
54 changes: 42 additions & 12 deletions internal/integration/api/selinux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import (
"bytes"
"context"
"io"
"maps"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/siderolabs/go-pointer"
"github.com/siderolabs/go-procfs/procfs"
"golang.org/x/exp/slices"

"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers"
"github.com/siderolabs/talos/internal/integration/base"
Expand Down Expand Up @@ -67,43 +69,65 @@ func (suite *SELinuxSuite) getLabel(nodeCtx context.Context, pid int32) string {
return string(bytes.TrimSpace(value))
}

// TestRuntimeFileLabels reads labels of runtime-created files from xattrs
// to ensure SELinux labels for files are set when they are created.
func (suite *SELinuxSuite) TestRuntimeFileLabels() {
// TestFileMountLabels reads labels of runtime-created files and mounts from xattrs
// to ensure SELinux labels for files are set when they are created and FS's are mounted with correct labels.
// FIXME: cancel the test in case system was upgraded.
func (suite *SELinuxSuite) TestFileMountLabels() {
workers := suite.DiscoverNodeInternalIPsByType(suite.ctx, machine.TypeWorker)
controlplanes := suite.DiscoverNodeInternalIPsByType(suite.ctx, machine.TypeControlPlane)

expectedLabelsWorker := map[string]string{
// Mounts
constants.SystemPath: constants.SystemSelinuxLabel,
constants.EphemeralMountPoint: constants.EphemeralSelinuxLabel,
constants.StateMountPoint: constants.StateSelinuxLabel,
constants.SystemEtcPath: constants.SystemEtcSelinuxLabel,
constants.SystemVarPath: constants.SystemVarSelinuxLabel,
constants.RunPath: constants.RunSelinuxLabel,
"/var/run": constants.RunSelinuxLabel,
// Runtime files
constants.APIRuntimeSocketPath: constants.APIRuntimeSocketLabel,
constants.APISocketPath: constants.APISocketLabel,
constants.DBusClientSocketPath: constants.DBusClientSocketLabel,
constants.UdevRulesPath: constants.UdevRulesLabel,
constants.DBusServiceSocketPath: constants.DBusServiceSocketLabel,
constants.MachineSocketPath: constants.MachineSocketLabel,
// Overlays
"/etc/cni": constants.CNISELinuxLabel,
constants.KubernetesConfigBaseDir: constants.KubernetesConfigSELinuxLabel,
"/usr/libexec/kubernetes": constants.KubeletPluginsSELinuxLabel,
"/opt": constants.OptSELinuxLabel,
"/opt/cni": "system_u:object_r:cni_plugin_t:s0",
"/opt/containerd": "system_u:object_r:containerd_plugin_t:s0",
// Directories
"/var/lib/containerd": "system_u:object_r:containerd_state_t:s0",
"/var/lib/kubelet": "system_u:object_r:kubelet_state_t:s0",
}

// Only running on controlplane
expectedLabelsControlPlane := map[string]string{
constants.APIRuntimeSocketPath: constants.APIRuntimeSocketLabel,
constants.APISocketPath: constants.APISocketLabel,
constants.DBusClientSocketPath: constants.DBusClientSocketLabel,
constants.UdevRulesPath: constants.UdevRulesLabel,
constants.DBusServiceSocketPath: constants.DBusServiceSocketLabel,
constants.MachineSocketPath: constants.MachineSocketLabel,
// Only running on controlplane
constants.EtcdPKIPath: constants.EtcdPKISELinuxLabel,
constants.EtcdDataPath: constants.EtcdDataSELinuxLabel,
constants.KubernetesAPIServerConfigDir: constants.KubernetesAPIServerConfigDirSELinuxLabel,
constants.KubernetesAPIServerSecretsDir: constants.KubernetesAPIServerSecretsDirSELinuxLabel,
constants.KubernetesControllerManagerSecretsDir: constants.KubernetesControllerManagerSecretsDirSELinuxLabel,
constants.KubernetesSchedulerConfigDir: constants.KubernetesSchedulerConfigDirSELinuxLabel,
constants.KubernetesSchedulerSecretsDir: constants.KubernetesSchedulerSecretsDirSELinuxLabel,
constants.TrustdRuntimeSocketPath: constants.TrustdRuntimeSocketLabel,
}
maps.Copy(expectedLabelsControlPlane, expectedLabelsWorker)

suite.checkFileLabels(workers, expectedLabelsWorker)
suite.checkFileLabels(controlplanes, expectedLabelsControlPlane)
}

//nolint:gocyclo
func (suite *SELinuxSuite) checkFileLabels(nodes []string, expectedLabels map[string]string) {
paths := make([]string, 0, len(expectedLabels))
for k := range expectedLabels {
paths = append(paths, k)
}

for _, node := range nodes {
nodeCtx := client.WithNode(suite.ctx, node)
cmdline := suite.ReadCmdline(nodeCtx)
Expand All @@ -129,14 +153,19 @@ func (suite *SELinuxSuite) checkFileLabels(nodes []string, expectedLabels map[st
suite.Require().NoError(err)

suite.Require().NoError(helpers.ReadGRPCStream(stream, func(info *machineapi.FileInfo, node string, multipleNodes bool) error {
// E.g. /var/lib should inherit /var label, while /var/run is a new mountpoint
if slices.Contains(paths, info.Name) && info.Name != path {
return nil
}

suite.Require().NotNil(info.Xattrs)

found := false

for _, l := range info.Xattrs {
if l.Name == "security.selinux" {
got := string(bytes.Trim(l.Data, "\x00\n"))
suite.Require().Equal(got, label, "expected %s to have label %s, got %s", path, label, got)
suite.Require().Contains(got, label, "expected %s to have label %s, got %s", path, label, got)

found = true

Expand Down Expand Up @@ -225,7 +254,8 @@ func (suite *SELinuxSuite) TestProcessLabels() {
}
}

// TODO: test for volume labels
// TODO: test for all machined-created files
// TODO: test for system and CRI container labels
// TODO: test labels for unconfined system extensions, pods
// TODO: test for no avc denials in dmesg
// TODO: start a pod and ensure access to restricted resources is denied
Expand Down
6 changes: 3 additions & 3 deletions internal/integration/k8s/tink.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,10 +420,10 @@ func (suite *TinkSuite) getTinkManifests(namespace, serviceName, ssName, talosIm
},
},
xslices.Map(
xslices.Filter(constants.Overlays, func(overlay string) bool { return overlay != "/opt" }), // /opt/cni/bin contains CNI binaries
func(mountPath string) overlayMountSpec {
xslices.Filter(constants.Overlays, func(overlay constants.SELinuxLabeledPath) bool { return overlay.Path != "/opt" }), // /opt/cni/bin contains CNI binaries
func(mnt constants.SELinuxLabeledPath) overlayMountSpec {
return overlayMountSpec{
MountPoint: mountPath,
MountPoint: mnt.Path,
Size: "100Mi",
}
},
Expand Down
2 changes: 2 additions & 0 deletions internal/pkg/mount/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func SystemPartitionMount(ctx context.Context, r runtime.Runtime, logger *log.Lo
return fmt.Errorf("error getting volume config %q: %w", label, err)
}

opts = append(opts, mountv2.WithSelinuxLabel(volumeConfig.TypedSpec().Mount.SelinuxLabel))

mountpoint := mountv2.NewPoint(
volumeStatus.TypedSpec().MountLocation,
volumeConfig.TypedSpec().Mount.TargetPath,
Expand Down
19 changes: 16 additions & 3 deletions internal/pkg/mount/v2/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/siderolabs/go-retry/retry"
"golang.org/x/sys/unix"

"github.com/siderolabs/talos/internal/pkg/selinux"
"github.com/siderolabs/talos/pkg/machinery/constants"
)

Expand All @@ -29,8 +30,9 @@ type Point struct {
flags uintptr
data string

shared bool
extraDirs []string
shared bool
extraDirs []string
selinuxLabel string
}

// NewPointOption is a mount point option.
Expand Down Expand Up @@ -84,6 +86,13 @@ func WithExtraDirs(dirs ...string) NewPointOption {
}
}

// WithSelinuxLabel sets the mount SELinux label.
func WithSelinuxLabel(label string) NewPointOption {
return func(p *Point) {
p.selinuxLabel = label
}
}

// NewPoint creates a new mount point.
func NewPoint(source, target, fstype string, opts ...NewPointOption) *Point {
p := &Point{
Expand Down Expand Up @@ -290,7 +299,11 @@ func (p *Point) Move(newTarget string) error {
}

func (p *Point) mount() error {
return unix.Mount(p.source, p.target, p.fstype, p.flags, p.data)
if err := unix.Mount(p.source, p.target, p.fstype, p.flags, p.data); err != nil {
return err
}

return selinux.SetLabel(p.target, p.selinuxLabel)
}

func (p *Point) unmount(printer func(string, ...any)) error {
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/mount/v2/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// OverlayMountPoints returns the mountpoints required to boot the system.
// These mountpoints are used as overlays on top of the read only rootfs.
func OverlayMountPoints() Points {
return xslices.Map(constants.Overlays, func(target string) *Point {
return NewVarOverlay([]string{target}, target, WithFlags(unix.MS_I_VERSION))
return xslices.Map(constants.Overlays, func(target constants.SELinuxLabeledPath) *Point {
return NewVarOverlay([]string{target.Path}, target.Path, WithFlags(unix.MS_I_VERSION), WithSelinuxLabel(target.Label))
})
}
4 changes: 2 additions & 2 deletions internal/pkg/mount/v2/pseudo.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ func Pseudo() Points {
// PseudoLate returns the mountpoints mounted later in the boot cycle.
func PseudoLate() Points {
return Points{
NewPoint("tmpfs", "/run", "tmpfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_RELATIME), WithData("mode=0755")),
NewPoint("tmpfs", "/system", "tmpfs", WithData("mode=0755")),
NewPoint("tmpfs", "/run", "tmpfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_RELATIME), WithData("mode=0755"), WithSelinuxLabel(constants.RunSelinuxLabel)),
NewPoint("tmpfs", "/system", "tmpfs", WithData("mode=0755"), WithSelinuxLabel(constants.SystemSelinuxLabel)),
NewPoint("tmpfs", "/tmp", "tmpfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV), WithData("size=64M"), WithData("mode=0755")),
}
}
Expand Down
6 changes: 6 additions & 0 deletions internal/pkg/selinux/policy/file_contexts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/opt(/.*)? system_u:object_r:opt_t:s0
/sbin(/.*)? system_u:object_r:sbin_exec_t:s0
/etc/cni(/.*)? system_u:object_r:cni_conf_t:s0
/opt/cni(/.*)? system_u:object_r:cni_plugin_t:s0
/usr/sbin(/.*)? system_u:object_r:sbin_exec_t:s0
/usr/lib/udev(/.*)? system_u:object_r:udev_exec_t:s0
/etc/kubernetes(/.*)? system_u:object_r:k8s_conf_t:s0
/opt/containerd(/.*)? system_u:object_r:containerd_plugin_t:s0
/usr/lib/udev/rules.d(/.*)? system_u:object_r:udev_rules_t:s0
/usr/libexec/kubernetes(/.*)? system_u:object_r:k8s_plugin_t:s0
/ system_u:object_r:rootfs_t:s0
/bin/runc system_u:object_r:containerd_exec_t:s0
/sbin/init -- system_u:object_r:init_exec_t:s0
Expand Down
Binary file modified internal/pkg/selinux/policy/policy.33
Binary file not shown.
Loading

0 comments on commit 1a8cc5f

Please sign in to comment.