From 584eca957c3e990ee2da9fadeab66da0d88930e5 Mon Sep 17 00:00:00 2001 From: shimritproj Date: Mon, 8 Apr 2024 23:09:51 +0300 Subject: [PATCH] Check for the recommended security requirements of the container-native operators checks: USER id should not be 0 readOnlyRootFilesystem = true runAsNonRoot = true automount service account token = false{} --- CATALOG.md | 72 ++++++++- cnf-certification-test/accesscontrol/suite.go | 2 +- .../rbac/automount.go | 0 .../rbac/automount_test.go | 0 .../{accesscontrol => common}/rbac/roles.go | 0 .../rbac/roles_test.go | 0 .../identifiers/doclinks.go | 4 + .../identifiers/identifiers.go | 68 +++++++++ .../identifiers/remediation.go | 8 + cnf-certification-test/operator/suite.go | 137 ++++++++++++++++++ cnf-certification-test/operator/suite_test.go | 69 +++++++++ expected_results.yaml | 4 + pkg/provider/containers.go | 5 + pkg/provider/containers_test.go | 40 +++++ pkg/provider/pods.go | 19 +++ pkg/provider/pods_test.go | 49 +++++++ 16 files changed, 472 insertions(+), 5 deletions(-) rename cnf-certification-test/{accesscontrol => common}/rbac/automount.go (100%) rename cnf-certification-test/{accesscontrol => common}/rbac/automount_test.go (100%) rename cnf-certification-test/{accesscontrol => common}/rbac/roles.go (100%) rename cnf-certification-test/{accesscontrol => common}/rbac/roles_test.go (100%) diff --git a/CATALOG.md b/CATALOG.md index 3a550f811b..b164772898 100644 --- a/CATALOG.md +++ b/CATALOG.md @@ -7,7 +7,7 @@ Depending on the workload type, not all tests are required to pass to satisfy be ## Test cases summary -### Total test cases: 109 +### Total test cases: 113 ### Total suites: 10 @@ -19,7 +19,7 @@ Depending on the workload type, not all tests are required to pass to satisfy be |manageability|2| |networking|11| |observability|4| -|operator|7| +|operator|11| |performance|6| |platform-alteration|13| |preflight|17| @@ -36,11 +36,11 @@ Depending on the workload type, not all tests are required to pass to satisfy be |---|---| |7|1| -### Non-Telco specific tests only: 62 +### Non-Telco specific tests only: 66 |Mandatory|Optional| |---|---| -|42|20| +|46|20| ### Telco specific tests only: 27 @@ -1122,6 +1122,22 @@ Tags|telco,observability ### operator +#### operator-automount-tokens + +Property|Description +---|--- +Unique ID|operator-automount-tokens +Description|Tests that checks the automount service account token is disabled. +Suggested Remediation|Ensure that the automount service account token is disabled. +Best Practice Reference|https://test-network-function.github.io/cnf-best-practices-guide/#cnf-best-practices-cnf-operator-requirements +Exception Process|No exceptions +Tags|common,operator +|**Scenario**|**Optional/Mandatory**| +|Extended|Mandatory| +|Far-Edge|Mandatory| +|Non-Telco|Mandatory| +|Telco|Mandatory| + #### operator-crd-openapi-schema Property|Description @@ -1202,6 +1218,54 @@ Tags|common,operator |Non-Telco|Mandatory| |Telco|Mandatory| +#### operator-read-only-file-system + +Property|Description +---|--- +Unique ID|operator-read-only-file-system +Description|Tests that checks the read-only root filesystem setting is enabled. +Suggested Remediation|Ensure that the read-only root filesystem setting is enabled. +Best Practice Reference|https://test-network-function.github.io/cnf-best-practices-guide/#cnf-best-practices-cnf-operator-requirements +Exception Process|No exceptions +Tags|common,operator +|**Scenario**|**Optional/Mandatory**| +|Extended|Mandatory| +|Far-Edge|Mandatory| +|Non-Telco|Mandatory| +|Telco|Mandatory| + +#### operator-run-as-non-root + +Property|Description +---|--- +Unique ID|operator-run-as-non-root +Description|Tests that checks run as non root. +Suggested Remediation|Ensure that run as non root. +Best Practice Reference|https://test-network-function.github.io/cnf-best-practices-guide/#cnf-best-practices-cnf-operator-requirements +Exception Process|No exceptions +Tags|common,operator +|**Scenario**|**Optional/Mandatory**| +|Extended|Mandatory| +|Far-Edge|Mandatory| +|Non-Telco|Mandatory| +|Telco|Mandatory| + +#### operator-run-as-user-id + +Property|Description +---|--- +Unique ID|operator-run-as-user-id +Description|Tests that checks user id should not be 0. +Suggested Remediation|Ensure that user id should not be 0. +Best Practice Reference|https://test-network-function.github.io/cnf-best-practices-guide/#cnf-best-practices-cnf-operator-requirements +Exception Process|No exceptions +Tags|common,operator +|**Scenario**|**Optional/Mandatory**| +|Extended|Mandatory| +|Far-Edge|Mandatory| +|Non-Telco|Mandatory| +|Telco|Mandatory| + #### operator-semantic-versioning Property|Description diff --git a/cnf-certification-test/accesscontrol/suite.go b/cnf-certification-test/accesscontrol/suite.go index 567157ca22..5c3bb3d385 100644 --- a/cnf-certification-test/accesscontrol/suite.go +++ b/cnf-certification-test/accesscontrol/suite.go @@ -23,10 +23,10 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/accesscontrol/namespace" - "github.com/test-network-function/cnf-certification-test/cnf-certification-test/accesscontrol/rbac" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/accesscontrol/resources" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/accesscontrol/securitycontextcontainer" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/common" + "github.com/test-network-function/cnf-certification-test/cnf-certification-test/common/rbac" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/identifiers" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/networking/netutil" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/networking/services" diff --git a/cnf-certification-test/accesscontrol/rbac/automount.go b/cnf-certification-test/common/rbac/automount.go similarity index 100% rename from cnf-certification-test/accesscontrol/rbac/automount.go rename to cnf-certification-test/common/rbac/automount.go diff --git a/cnf-certification-test/accesscontrol/rbac/automount_test.go b/cnf-certification-test/common/rbac/automount_test.go similarity index 100% rename from cnf-certification-test/accesscontrol/rbac/automount_test.go rename to cnf-certification-test/common/rbac/automount_test.go diff --git a/cnf-certification-test/accesscontrol/rbac/roles.go b/cnf-certification-test/common/rbac/roles.go similarity index 100% rename from cnf-certification-test/accesscontrol/rbac/roles.go rename to cnf-certification-test/common/rbac/roles.go diff --git a/cnf-certification-test/accesscontrol/rbac/roles_test.go b/cnf-certification-test/common/rbac/roles_test.go similarity index 100% rename from cnf-certification-test/accesscontrol/rbac/roles_test.go rename to cnf-certification-test/common/rbac/roles_test.go diff --git a/cnf-certification-test/identifiers/doclinks.go b/cnf-certification-test/identifiers/doclinks.go index 650a9c4188..4fc070a477 100644 --- a/cnf-certification-test/identifiers/doclinks.go +++ b/cnf-certification-test/identifiers/doclinks.go @@ -110,6 +110,10 @@ const ( TestOperatorCrdSchemaIdentifierDocLink = DocOperatorRequirement TestOperatorCrdVersioningIdentifierDocLink = DocOperatorRequirement TestOperatorSingleCrdOwnerIdentifierDocLink = DocOperatorRequirement + TestOperatorRunAsUserIDDocLink = DocOperatorRequirement + TestOperatorRunAsNonRootDocLink = DocOperatorRequirement + TestOperatorAutomountTokensDocLink = DocOperatorRequirement + TestOperatorReadOnlyFilesystemDocLink = DocOperatorRequirement // Observability Test Suite TestLoggingIdentifierDocLink = "https://test-network-function.github.io/cnf-best-practices-guide/#cnf-best-practices-logging" diff --git a/cnf-certification-test/identifiers/identifiers.go b/cnf-certification-test/identifiers/identifiers.go index 4902a905fb..bc90c2e71d 100644 --- a/cnf-certification-test/identifiers/identifiers.go +++ b/cnf-certification-test/identifiers/identifiers.go @@ -122,6 +122,10 @@ var ( TestHelmIsCertifiedIdentifier claim.Identifier TestOperatorIsInstalledViaOLMIdentifier claim.Identifier TestOperatorHasSemanticVersioningIdentifier claim.Identifier + TestOperatorReadOnlyFilesystem claim.Identifier + TestOperatorAutomountTokens claim.Identifier + TestOperatorRunAsNonRoot claim.Identifier + TestOperatorRunAsUserID claim.Identifier TestOperatorCrdVersioningIdentifier claim.Identifier TestOperatorCrdSchemaIdentifier claim.Identifier TestOperatorSingleCrdOwnerIdentifier claim.Identifier @@ -930,6 +934,70 @@ that Node's kernel may not have the same hacks.'`, }, TagCommon) + TestOperatorRunAsUserID = AddCatalogEntry( + "run-as-user-id", + common.OperatorTestKey, + `Tests that checks user id should not be 0.`, + OperatorRunAsUserID, + NoExceptions, + TestOperatorRunAsUserIDDocLink, + true, + map[string]string{ + FarEdge: Mandatory, + Telco: Mandatory, + NonTelco: Mandatory, + Extended: Mandatory, + }, + TagCommon) + + TestOperatorRunAsNonRoot = AddCatalogEntry( + "run-as-non-root", + common.OperatorTestKey, + `Tests that checks run as non root.`, + OperatorRunAsNonRoot, + NoExceptions, + TestOperatorRunAsNonRootDocLink, + true, + map[string]string{ + FarEdge: Mandatory, + Telco: Mandatory, + NonTelco: Mandatory, + Extended: Mandatory, + }, + TagCommon) + + TestOperatorAutomountTokens = AddCatalogEntry( + "automount-tokens", + common.OperatorTestKey, + `Tests that checks the automount service account token is disabled.`, + OperatorAutomountTokens, + NoExceptions, + TestOperatorAutomountTokensDocLink, + true, + map[string]string{ + FarEdge: Mandatory, + Telco: Mandatory, + NonTelco: Mandatory, + Extended: Mandatory, + }, + TagCommon) + + TestOperatorReadOnlyFilesystem = AddCatalogEntry( + "read-only-file-system", + common.OperatorTestKey, + `Tests that checks the read-only root filesystem setting is enabled.`, + OperatorReadOnlyFilesystem, + NoExceptions, + TestOperatorReadOnlyFilesystemDocLink, + true, + map[string]string{ + FarEdge: Mandatory, + Telco: Mandatory, + NonTelco: Mandatory, + Extended: Mandatory, + }, + TagCommon) + TestOperatorCrdVersioningIdentifier = AddCatalogEntry( "crd-versioning", common.OperatorTestKey, diff --git a/cnf-certification-test/identifiers/remediation.go b/cnf-certification-test/identifiers/remediation.go index cdf57be9c7..15e3f13dfa 100644 --- a/cnf-certification-test/identifiers/remediation.go +++ b/cnf-certification-test/identifiers/remediation.go @@ -83,6 +83,14 @@ const ( OperatorCrdSchemaIdentifierRemediation = `Ensure that the Operator CRD is defined with OpenAPI spec.` + OperatorRunAsUserID = `Ensure that user id should not be 0.` + + OperatorRunAsNonRoot = `Ensure that run as non root.` + + OperatorAutomountTokens = `Ensure that the automount service account token is disabled.` + + OperatorReadOnlyFilesystem = `Ensure that the read-only root filesystem setting is enabled.` + OperatorCrdVersioningRemediation = `Ensure that the Operator CRD has a valid version.` OperatorSingleCrdOwnerRemediation = `Ensure that a CRD is owned by only one Operator` diff --git a/cnf-certification-test/operator/suite.go b/cnf-certification-test/operator/suite.go index 942600d726..8956b74e74 100644 --- a/cnf-certification-test/operator/suite.go +++ b/cnf-certification-test/operator/suite.go @@ -17,11 +17,14 @@ package operator import ( + "fmt" "strings" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/common" + "github.com/test-network-function/cnf-certification-test/cnf-certification-test/common/rbac" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/identifiers" "github.com/test-network-function/cnf-certification-test/cnf-certification-test/operator/phasecheck" + "github.com/test-network-function/cnf-certification-test/internal/clientsholder" "github.com/test-network-function/cnf-certification-test/internal/log" "github.com/test-network-function/cnf-certification-test/pkg/checksdb" @@ -39,6 +42,7 @@ var ( } ) +//nolint:funlen func LoadChecks() { log.Debug("Loading %s suite checks", common.OperatorTestKey) @@ -93,6 +97,33 @@ func LoadChecks() { testOperatorSingleCrdOwner(c, &env) return nil })) + + checksGroup.Add(checksdb.NewCheck(identifiers.GetTestIDAndLabels(identifiers.TestOperatorRunAsUserID)). + WithSkipCheckFn(testhelper.GetNoOperatorsSkipFn(&env)). + WithCheckFn(func(c *checksdb.Check) error { + testOperatorRunAsUserID(c, &env) + return nil + })) + checksGroup.Add(checksdb.NewCheck(identifiers.GetTestIDAndLabels(identifiers.TestOperatorRunAsNonRoot)). + WithSkipCheckFn(testhelper.GetNoOperatorsSkipFn(&env)). + WithCheckFn(func(c *checksdb.Check) error { + testOperatorRunAsNonRoot(c, &env) + return nil + })) + + checksGroup.Add(checksdb.NewCheck(identifiers.GetTestIDAndLabels(identifiers.TestOperatorAutomountTokens)). + WithSkipCheckFn(testhelper.GetNoOperatorsSkipFn(&env)). + WithCheckFn(func(c *checksdb.Check) error { + testOperatorAutomountTokens(c, &env) + return nil + })) + + checksGroup.Add(checksdb.NewCheck(identifiers.GetTestIDAndLabels(identifiers.TestOperatorReadOnlyFilesystem)). + WithSkipCheckFn(testhelper.GetNoOperatorsSkipFn(&env)). + WithCheckFn(func(c *checksdb.Check) error { + testOperatorReadOnlyFilesystem(c, &env) + return nil + })) } // This function check if the Operator CRD version follows K8s versioning @@ -318,3 +349,109 @@ func testOperatorSingleCrdOwner(check *checksdb.Check, env *provider.TestEnviron check.SetResult(compliantObjects, nonCompliantObjects) } + +func testOperatorRunAsUserID(check *checksdb.Check, env *provider.TestEnvironment) { + var compliantObjects []*testhelper.ReportObject + var nonCompliantObjects []*testhelper.ReportObject + for i := range env.Operators { + operator := env.Operators[i] + check.LogInfo("Testing Operator %q", operator) + AllPods := findAllPods(env, operator) + for _, put := range AllPods { + check.LogInfo("Testing Pod %q", put) + if put.IsRunAsUserID(0) { + check.LogError("Pod %q UserID is 0", put.Name) + nonCompliantObjects = append(nonCompliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, "Pod has been found with UserID is 0", false)) + } else { + check.LogInfo("Pod %q UserID is not 0", put.Name) + compliantObjects = append(compliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, "Pod has been found with UserID is not 0", true)) + } + } + } + check.SetResult(compliantObjects, nonCompliantObjects) +} + +func testOperatorRunAsNonRoot(check *checksdb.Check, env *provider.TestEnvironment) { + var compliantObjects []*testhelper.ReportObject + var nonCompliantObjects []*testhelper.ReportObject + for i := range env.Operators { + operator := env.Operators[i] + check.LogInfo("Testing Operator %q", operator) + AllPods := findAllPods(env, operator) + for _, put := range AllPods { + check.LogInfo("Testing Pod %q", put) + if put.IsRunAsNonRoot() { + check.LogInfo("Pod %q is run as not root", put.Name) + compliantObjects = append(compliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, "Pod has been found is run as not root", true)) + } else { + check.LogError("Pod %q is run as root", put.Name) + nonCompliantObjects = append(nonCompliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, "Pod has been found is run as root", false)) + } + } + } + check.SetResult(compliantObjects, nonCompliantObjects) +} + +func testOperatorAutomountTokens(check *checksdb.Check, env *provider.TestEnvironment) { + var compliantObjects []*testhelper.ReportObject + var nonCompliantObjects []*testhelper.ReportObject + for i := range env.Operators { + operator := env.Operators[i] + check.LogInfo("Testing Operator %q", operator) + AllPods := findAllPods(env, operator) + for _, put := range AllPods { + check.LogInfo("Testing Pod %q", put) + // Evaluate the pod's automount service tokens and any attached service accounts + client := clientsholder.GetClientsHolder() + podPassed, newMsg := rbac.EvaluateAutomountTokens(client.K8sClient.CoreV1(), put.Pod) + if !podPassed { + check.LogInfo("Pod %q have automount service tokens set to false", put) + compliantObjects = append(compliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, "Pod %q have automount service tokens set to false", true)) + } else { + check.LogError(newMsg) + nonCompliantObjects = append(nonCompliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, newMsg, false)) + } + } + } + check.SetResult(compliantObjects, nonCompliantObjects) +} + +func testOperatorReadOnlyFilesystem(check *checksdb.Check, env *provider.TestEnvironment) { + var compliantObjects []*testhelper.ReportObject + var nonCompliantObjects []*testhelper.ReportObject + for i := range env.Operators { + operator := env.Operators[i] + check.LogInfo("Testing Operator %q", operator) + AllPods := findAllPods(env, operator) + for _, put := range AllPods { + check.LogInfo("Testing Pod %q", put) + for _, cut := range put.Containers { + check.LogInfo("Testing Container %q", cut.Name) + if cut.IsReadOnlyRootFilesystem(check.GetLoggger()) { + check.LogInfo("Pod %q container %q is read only root file system.", put.Name, cut.Name) + compliantObjects = append(compliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, "Pod has been found read only root file system", true)) + } else { + check.LogError("Pod %q container %q is read not only root file system.", put.Name, cut.Name) + nonCompliantObjects = append(nonCompliantObjects, testhelper.NewPodReportObject(put.Namespace, put.Name, "Pod has been found read not only root file system", false)) + } + } + } + } + check.SetResult(compliantObjects, nonCompliantObjects) +} + +func findAllPods(env *provider.TestEnvironment, operator *provider.Operator) []*provider.Pod { + var AllPods []*provider.Pod + for _, put := range env.AllPods { + fmt.Printf("pod: %v", put) + for _, ownerRef := range put.ObjectMeta.GetOwnerReferences() { + for _, ownerRefOperator := range operator.Csv.ObjectMeta.OwnerReferences { + if ownerRef.Kind == ownerRefOperator.Kind && ownerRef.Name == ownerRefOperator.Name { + AllPods = append(AllPods, put) + break + } + } + } + } + return AllPods +} diff --git a/cnf-certification-test/operator/suite_test.go b/cnf-certification-test/operator/suite_test.go index c56a13b8bf..de19eea9b5 100644 --- a/cnf-certification-test/operator/suite_test.go +++ b/cnf-certification-test/operator/suite_test.go @@ -15,3 +15,72 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. package operator + +import ( + "testing" + + olmv1Alpha "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/test-network-function/cnf-certification-test/pkg/provider" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestFindAllPods(t *testing.T) { + tests := []struct { + env *provider.TestEnvironment + operator *provider.Operator + exceptResult []*provider.Pod + }{ + { + env: &provider.TestEnvironment{ + AllPods: []*provider.Pod{ + { + Pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testpod", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "Pod", + Name: "nginx-pod", + }, + }, + }, + }, + }, + }, + }, + operator: &provider.Operator{ + Csv: &olmv1Alpha.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "Pod", + Name: "nginx-pod", + }, + }, + }, + }, + }, + exceptResult: []*provider.Pod{ + { + Pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testpod", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "Pod", + Name: "nginx-pod", + }, + }, + }, + }, + }, + }, + }, + } + for _, tc := range tests { + result := findAllPods(tc.env, tc.operator) + assert.Equal(t, tc.exceptResult, result) + } +} diff --git a/expected_results.yaml b/expected_results.yaml index 0c56047ea9..c1f1353b36 100644 --- a/expected_results.yaml +++ b/expected_results.yaml @@ -54,11 +54,15 @@ testCases: - observability-crd-status - observability-pod-disruption-budget - observability-termination-policy + - operator-automount-tokens - operator-crd-openapi-schema - operator-crd-versioning - operator-install-source - operator-install-status-no-privileges - operator-install-status-succeeded + - operator-read-only-file-system + - operator-run-as-non-root + - operator-run-as-user-id - operator-semantic-versioning - operator-single-crd-owner - performance-exclusive-cpu-pool diff --git a/pkg/provider/containers.go b/pkg/provider/containers.go index d53e21a047..6901590776 100644 --- a/pkg/provider/containers.go +++ b/pkg/provider/containers.go @@ -185,3 +185,8 @@ func (c *Container) HasExecProbes() bool { func (c *Container) IsTagEmpty() bool { return c.ContainerImageIdentifier.Tag == "" } + +func (c *Container) IsReadOnlyRootFilesystem(logger *log.Logger) bool { + logger.Info("Testing Container %q", c.Name) + return *(c.SecurityContext.ReadOnlyRootFilesystem) +} diff --git a/pkg/provider/containers_test.go b/pkg/provider/containers_test.go index af4779c461..5402042598 100644 --- a/pkg/provider/containers_test.go +++ b/pkg/provider/containers_test.go @@ -17,9 +17,11 @@ package provider import ( + "os" "testing" "github.com/stretchr/testify/assert" + "github.com/test-network-function/cnf-certification-test/internal/log" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -204,3 +206,41 @@ func TestIsTagEmpty(t *testing.T) { assert.Equal(t, tc.expectedOutput, tc.testContainer.IsTagEmpty()) } } + +func TestIsreadOnlyRootFilessystem(t *testing.T) { + trueVal := true + falseVal := false + testCases := []struct { + testContainer Container + expectedOutput bool + }{ + { + testContainer: Container{ + Container: &corev1.Container{ + Name: "TestContainer1", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: &trueVal, + }, + }, + }, + expectedOutput: true, + }, + { + testContainer: Container{ + Container: &corev1.Container{ + Name: "TestContainer2", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: &falseVal, + }, + }, + }, + expectedOutput: false, + }, + } + + for _, tc := range testCases { + log.SetupLogger(os.Stdout, "INFO") + actualOutput := tc.testContainer.IsReadOnlyRootFilesystem(log.GetLogger()) + assert.Equal(t, tc.expectedOutput, actualOutput) + } +} diff --git a/pkg/provider/pods.go b/pkg/provider/pods.go index dfef2fa484..ed14df0c49 100644 --- a/pkg/provider/pods.go +++ b/pkg/provider/pods.go @@ -415,6 +415,25 @@ func (p *Pod) GetTopOwner() (topOwners map[string]TopOwner, err error) { return topOwners, nil } +func (p *Pod) IsRunAsNonRoot() bool { + if p == nil || p.Pod == nil { + return false + } + + // Check if SecurityContext is nil + if p.Pod.Spec.SecurityContext == nil { + return false + } + + // Check if RunAsNonRoot is nil + if p.Pod.Spec.SecurityContext.RunAsNonRoot == nil { + return false + } + + // Return the value of RunAsNonRoot + return *p.Pod.Spec.SecurityContext.RunAsNonRoot +} + // Structure to describe a top owner of a pod type TopOwner struct { Kind string diff --git a/pkg/provider/pods_test.go b/pkg/provider/pods_test.go index 533887a578..b5d174c3de 100644 --- a/pkg/provider/pods_test.go +++ b/pkg/provider/pods_test.go @@ -599,3 +599,52 @@ func Test_followOwnerReferences(t *testing.T) { }) } } + +func TestIsRunAsNonRoot(t *testing.T) { + tests := []struct { + Pod *Pod + ExpectedResult bool + }{ + { + Pod: nil, + ExpectedResult: false, + }, + { + Pod: &Pod{ + Pod: &corev1.Pod{}, + }, + ExpectedResult: false, + }, + { + Pod: &Pod{ + Pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: boolPointer(true), + }, + }, + }, + }, + ExpectedResult: true, + }, + { + Pod: &Pod{ + Pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: boolPointer(false), + }, + }, + }, + }, + ExpectedResult: false, + }, + } + + for _, tc := range tests { + assert.Equal(t, tc.ExpectedResult, tc.Pod.IsRunAsNonRoot()) + } +} +func boolPointer(b bool) *bool { + return &b +}