mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-07 23:27:07 +02:00
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>
266 lines
8.6 KiB
Go
266 lines
8.6 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
//go:build integration_api
|
|
|
|
package api
|
|
|
|
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"
|
|
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
|
|
"github.com/siderolabs/talos/pkg/machinery/client"
|
|
"github.com/siderolabs/talos/pkg/machinery/config/machine"
|
|
"github.com/siderolabs/talos/pkg/machinery/constants"
|
|
)
|
|
|
|
// SELinuxSuite ...
|
|
type SELinuxSuite struct {
|
|
base.APISuite
|
|
|
|
ctx context.Context //nolint:containedctx
|
|
ctxCancel context.CancelFunc
|
|
}
|
|
|
|
// SuiteName ...
|
|
func (suite *SELinuxSuite) SuiteName() string {
|
|
return "api.SELinuxSuite"
|
|
}
|
|
|
|
// SetupTest ...
|
|
func (suite *SELinuxSuite) SetupTest() {
|
|
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 15*time.Second)
|
|
|
|
if suite.Cluster == nil || suite.Cluster.Provisioner() != base.ProvisionerQEMU {
|
|
suite.T().Skip("skipping SELinux test since provisioner is not qemu")
|
|
}
|
|
}
|
|
|
|
// TearDownTest ...
|
|
func (suite *SELinuxSuite) TearDownTest() {
|
|
if suite.ctxCancel != nil {
|
|
suite.ctxCancel()
|
|
}
|
|
}
|
|
|
|
func (suite *SELinuxSuite) getLabel(nodeCtx context.Context, pid int32) string {
|
|
r, err := suite.Client.Read(nodeCtx, filepath.Join("/proc", strconv.Itoa(int(pid)), "attr/current"))
|
|
suite.Require().NoError(err)
|
|
|
|
value, err := io.ReadAll(r)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().NoError(r.Close())
|
|
|
|
return string(bytes.TrimSpace(value))
|
|
}
|
|
|
|
// 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.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)
|
|
|
|
seLinuxEnabled := pointer.SafeDeref(procfs.NewCmdline(cmdline).Get(constants.KernelParamSELinux).First()) != ""
|
|
if !seLinuxEnabled {
|
|
suite.T().Skip("skipping SELinux test since SELinux is disabled")
|
|
}
|
|
|
|
// We should check both folders and their contents for proper labels
|
|
for _, dir := range []bool{true, false} {
|
|
for path, label := range expectedLabels {
|
|
req := &machineapi.ListRequest{
|
|
Root: path,
|
|
ReportXattrs: true,
|
|
}
|
|
if dir {
|
|
req.Types = []machineapi.ListRequest_Type{machineapi.ListRequest_DIRECTORY}
|
|
}
|
|
|
|
stream, err := suite.Client.LS(nodeCtx, req)
|
|
|
|
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().Contains(got, label, "expected %s to have label %s, got %s", path, label, got)
|
|
|
|
found = true
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
suite.Require().True(found)
|
|
|
|
return nil
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestProcessLabels reads labels of system processes from procfs
|
|
// to ensure SELinux labels for processes are correctly set
|
|
//
|
|
//nolint:gocyclo
|
|
func (suite *SELinuxSuite) TestProcessLabels() {
|
|
nodes := suite.DiscoverNodeInternalIPs(suite.ctx)
|
|
|
|
for _, node := range nodes {
|
|
nodeCtx := client.WithNode(suite.ctx, node)
|
|
cmdline := suite.ReadCmdline(nodeCtx)
|
|
|
|
seLinuxEnabled := pointer.SafeDeref(procfs.NewCmdline(cmdline).Get(constants.KernelParamSELinux).First()) != ""
|
|
if !seLinuxEnabled {
|
|
suite.T().Skip("skipping SELinux test since SELinux is disabled")
|
|
}
|
|
|
|
r, err := suite.Client.Processes(nodeCtx)
|
|
suite.Require().NoError(err)
|
|
|
|
for _, msg := range r.Messages {
|
|
procs := msg.Processes
|
|
|
|
for _, p := range procs {
|
|
switch p.Command {
|
|
case "systemd-udevd":
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelUdevd,
|
|
)
|
|
case "dashboard":
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelDashboard,
|
|
)
|
|
case "containerd":
|
|
if strings.Contains(p.Args, "/system/run/containerd") {
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelSystemRuntime,
|
|
)
|
|
} else {
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelPodRuntime,
|
|
)
|
|
}
|
|
case "init":
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelMachined,
|
|
)
|
|
case "kubelet":
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelKubelet,
|
|
)
|
|
case "apid":
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelApid,
|
|
)
|
|
case "trustd":
|
|
suite.Require().Contains(
|
|
suite.getLabel(nodeCtx, p.Pid),
|
|
constants.SelinuxLabelTrustd,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
func init() {
|
|
allSuites = append(allSuites, new(SELinuxSuite))
|
|
}
|