Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ require (
sigs.k8s.io/yaml v1.4.0
)

require github.com/opencontainers/selinux v1.13.1

require (
github.com/OneOfOne/xxhash v1.2.9-0.20201014161131-8506fca4db5e // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
Expand All @@ -49,6 +51,7 @@ require (
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/coreos/vcontext v0.0.0-20231102161604-685dc7299dc5 // indirect
github.com/cyphar/filepath-securejoin v0.5.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ github.com/coreos/vcontext v0.0.0-20231102161604-685dc7299dc5 h1:sMZSC2BW5LKCdvN
github.com/coreos/vcontext v0.0.0-20231102161604-685dc7299dc5/go.mod h1:Salmysdw7DAVuobBW/LwsKKgpyCPHUhjyJoMJD+ZJiI=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand Down Expand Up @@ -140,6 +142,8 @@ github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
github.com/openshift-kni/debug-tools v0.2.5 h1:YDHniE7pvEnveQPXPP2TOHcWbPzfpbcPsMJ2wu4vAEk=
github.com/openshift-kni/debug-tools v0.2.5/go.mod h1:kBoANbhBO3X7qXwrve0dcEtAM97Enrm7mF58p8RLLJI=
github.com/openshift/api v0.0.0-20250305013520-e7f23be12279 h1:eYvpiSNyNl7P7kmtNH0d6zAMLlrziyz370m0q1tggJE=
Expand Down
3 changes: 3 additions & 0 deletions must-gather/collection-scripts/gather
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ done
# Collect PFP debugging data
/usr/bin/gather_pfp

# Collect SELinux debugging data
/usr/bin/gather_selinux

exit 0
64 changes: 64 additions & 0 deletions must-gather/collection-scripts/gather_selinux
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash

. namespace
NRO_NAMESPACE=$(nro_namespace)

SELINUX_INFO_DIR="/must-gather/selinux_info"
mkdir -p "$SELINUX_INFO_DIR"

# common variables
OC_GET_PODS="oc get pods -n $NRO_NAMESPACE"

function replace_dot_with_underscore() {
echo $1 | sed 's/\./_/g'
}

function gather_selinux_data() {
local rte_pods=$($OC_GET_PODS -l name='resource-topology' -o jsonpath='{.items[*].metadata.name}')

for pod in $rte_pods; do
local original_node_name=$($OC_GET_PODS $pod -o jsonpath='{.spec.nodeName}')
local node_name=$(replace_dot_with_underscore $original_node_name)

echo "Gathering SELinux data from node: $node_name"

local node_dir="$SELINUX_INFO_DIR/$node_name"
mkdir -p "$node_dir"

oc debug node/$original_node_name -- bash -c "
chroot /host bash -c '
mkdir -p /tmp/selinux_data

echo \"=== SELinux context for /var/lib/kubelet ===\" > /tmp/selinux_data/contexts
ls -Z /var/lib/kubelet >> /tmp/selinux_data/contexts 2>&1
echo \"\" >> /tmp/selinux_data/contexts
echo \"=== SELinux context for kubelet.sock ===\" >> /tmp/selinux_data/contexts
ls -Z /var/lib/kubelet/pod-resources/kubelet.sock >> /tmp/selinux_data/contexts 2>&1

systemctl show kubelet.service > /tmp/selinux_data/kubelet_systemctl_show 2>&1

systemctl cat kubelet.service > /tmp/selinux_data/kubelet_systemctl_cat 2>&1

echo \"=== SELinux audit logs ===\" > /tmp/selinux_data/audit_selinux.log
grep -i selinux /var/log/audit/audit.log >> /tmp/selinux_data/audit_selinux.log 2>&1

echo \"=== Pod-resources related audit logs ===\" > /tmp/selinux_data/audit_podresources.log
grep \"kubelet.*pod-resources\" /var/log/audit/audit.log >> /tmp/selinux_data/audit_podresources.log 2>&1

tar czf - -C /tmp selinux_data
'
" 2>/dev/null | tar xzf - -C "$node_dir" --strip-components=1

if [ $? -eq 0 ]; then
echo "Successfully collected SELinux data from node: $node_name"
else
echo "Failed to collect SELinux data from node: $node_name"
fi
done
}

if [ -z "${NRO_NAMESPACE}" ]; then
echo "NUMAResources Operator namespace not detected. Skipping SELinux data gathering"
else
gather_selinux_data
fi
109 changes: 108 additions & 1 deletion test/e2e/must-gather/must_gather_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ import (
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/opencontainers/selinux/go-selinux"

corev1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"

Expand Down Expand Up @@ -81,8 +84,9 @@ var _ = Describe("[must-gather] NRO data collected", func() {
cmd := exec.Command(cmdline[0], cmdline[1:]...)
cmd.Stderr = GinkgoWriter

_, err = cmd.Output()
output, err := cmd.Output()
Expect(err).ToNot(HaveOccurred())
fmt.Fprintf(GinkgoWriter, "output: %s\n", string(output))
})

AfterEach(func() {
Expand Down Expand Up @@ -184,6 +188,109 @@ var _ = Describe("[must-gather] NRO data collected", func() {
Expect(podFolderNames).To(ContainElement(MatchRegexp("^secondary-scheduler*")))
}
})

It("check SELinux data files have been collected", func(ctx context.Context) {
minVersion, err := platform.ParseVersion("4.22")
Expect(err).ToNot(HaveOccurred(), "failed to parse minimum version")
supported, err := configuration.PlatVersion.AtLeast(minVersion)
Expect(err).ToNot(HaveOccurred(), "failed to compare versions: %v vs %v", configuration.PlatVersion, minVersion)
if !supported {
Skip(fmt.Sprintf("SELinux data collection is only supported on cluster version %s or newer, got %s", minVersion, configuration.PlatVersion))
}

destDirContent, err := os.ReadDir(destDir)
Expect(err).NotTo(HaveOccurred(), "unable to read contents from destDir:%s. error: %w", destDir, err)

for _, content := range destDirContent {
if !content.IsDir() {
continue
}
mgContentFolder := filepath.Join(destDir, content.Name())

By(fmt.Sprintf("Checking Folder: %q", mgContentFolder))
By("Looking for SELinux info directory")

selinuxInfoFolder := filepath.Join(mgContentFolder, "selinux_info")
_, err = os.Stat(selinuxInfoFolder)
Expect(err).ToNot(HaveOccurred(), "selinux_info directory should exist")

selinuxDirContent, err := os.ReadDir(selinuxInfoFolder)
Expect(err).NotTo(HaveOccurred())
Expect(selinuxDirContent).ToNot(BeEmpty(), "selinux_info directory should contain node directories")

// Check each node directory
for _, nodeDir := range selinuxDirContent {
if !nodeDir.IsDir() {
// We expect only directories in selinux_info, but check defensively
By(fmt.Sprintf("Warning: unexpected non-directory item in selinux_info: %s", nodeDir.Name()))
continue
}

nodeName := nodeDir.Name()
nodeSelinuxFolder := filepath.Join(selinuxInfoFolder, nodeName)
By(fmt.Sprintf("Checking SELinux data for node: %s", nodeName))

// Check required files exist
requiredFiles := []string{
"contexts",
"kubelet_systemctl_show",
"kubelet_systemctl_cat",
"audit_selinux.log",
"audit_podresources.log",
}
err = checkfilesExist(requiredFiles, nodeSelinuxFolder)
Expect(err).ToNot(HaveOccurred(), "required SELinux files should exist for node %s", nodeName)

// Verify contexts file contains kubelet.sock SELinux context
By(fmt.Sprintf("Verifying kubelet.sock SELinux context for node: %s", nodeName))
contextsFile := filepath.Join(nodeSelinuxFolder, "contexts")
contextsData, err := os.ReadFile(contextsFile)
Expect(err).ToNot(HaveOccurred())

contextsContent := string(contextsData)

// Parse SELinux context from the kubelet.sock line using official selinux package
By(fmt.Sprintf("Parsing SELinux context for kubelet.sock on node: %s", nodeName))
found := false
for _, line := range strings.Split(contextsContent, "\n") {
if strings.Contains(line, "/var/lib/kubelet/pod-resources/kubelet.sock") {
// Extract SELinux context from ls -Z output (format: "context filename")
parts := strings.Fields(line)
if len(parts) >= 2 {
selinuxLabel := parts[0]
context, err := selinux.NewContext(selinuxLabel)
Expect(err).ToNot(HaveOccurred(), "should parse SELinux context: %s", selinuxLabel)

// Check that the type field contains kubelet_var_lib_t
contextType := context["type"]
Expect(contextType).To(Equal("kubelet_var_lib_t"), "kubelet.sock should have kubelet_var_lib_t SELinux context type, got: %s", contextType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for 4.18+ (right?), the test like this will run d/s on all versions, and will fail. we can either mark this with a new label and d/s will adapt accordingly and run it only where supported (see https://github.com/openshift-kni/numaresources-operator/blob/main/internal/api/features/_topics.json)
Or if you want to backport for better debugging for older versions, then you need to update this version of the test to handle the old expected type too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch I'll update the test accordingly

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shajmakh Thinking about it again, are must gather test are part of the serial suite where same tests are running against all releases?

If so, and we want this test to run on 4.18+ we need to backport from 4.22->4.18, do we want that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tests runs also in d/s, so yes, we use the same test image for all releases. But also these tests run u/s via prow, so if we want to backport the data collection support to older versions, we need to have the test changes also backported so we can have coverage u/s.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I added skip if version < 4.22, for every backport I'll decrease the minimum version.


By(fmt.Sprintf("Found valid SELinux context for kubelet.sock: %s (type: %s)", selinuxLabel, contextType))
found = true
break
}
}
}
Expect(found).To(BeTrue(), "should find kubelet.sock with valid SELinux context in contexts file")

// Verify kubelet systemctl show has content
By(fmt.Sprintf("Verifying kubelet_systemctl_show content for node: %s", nodeName))
kubeletShowFile := filepath.Join(nodeSelinuxFolder, "kubelet_systemctl_show")
kubeletShowData, err := os.ReadFile(kubeletShowFile)
Expect(err).ToNot(HaveOccurred())
kubeletShowContent := string(kubeletShowData)
Expect(kubeletShowContent).To(ContainSubstring("ExecStart="), "kubelet_systemctl_show should contain ExecStart property")

// Verify kubelet systemctl cat has content
By(fmt.Sprintf("Verifying kubelet_systemctl_cat content for node: %s", nodeName))
kubeletCatFile := filepath.Join(nodeSelinuxFolder, "kubelet_systemctl_cat")
kubeletCatData, err := os.ReadFile(kubeletCatFile)
Expect(err).ToNot(HaveOccurred())
kubeletCatContent := string(kubeletCatData)
Expect(kubeletCatContent).To(ContainSubstring("/usr/sbin/restorecon"), "kubelet_systemctl_cat should contain restorecon command in ExecStartPre")
}
}
})
})
})

Expand Down
Loading
Loading