diff --git a/cmd/all_test.go b/cmd/all_test.go index c42b26e9..22ccb0d6 100644 --- a/cmd/all_test.go +++ b/cmd/all_test.go @@ -8,7 +8,7 @@ func TestAuditAllV1(t *testing.T) { requiredErrors := []int{ ErrorAllowPrivilegeEscalationNil, ErrorAutomountServiceAccountTokenNilAndNoName, ErrorCapabilityNotDropped, ErrorImageTagMissing, ErrorPrivilegedNil, ErrorReadOnlyRootFilesystemNil, ErrorResourcesLimitsNil, - ErrorRunAsNonRootNil, ErrorAppArmorAnnotationMissing, ErrorSeccompAnnotationMissing, + ErrorRunAsNonRootPSCNilCSCNil, ErrorAppArmorAnnotationMissing, ErrorSeccompAnnotationMissing, } runAuditTest(t, "audit_all_v1.yml", mergeAuditFunctions(allAuditFunctions), requiredErrors) } @@ -17,7 +17,7 @@ func TestAuditAllV1beta1(t *testing.T) { requiredErrors := []int{ ErrorAllowPrivilegeEscalationNil, ErrorAutomountServiceAccountTokenNilAndNoName, ErrorCapabilityNotDropped, ErrorImageTagMissing, ErrorPrivilegedNil, ErrorReadOnlyRootFilesystemNil, ErrorResourcesLimitsNil, - ErrorRunAsNonRootNil, ErrorAppArmorAnnotationMissing, ErrorSeccompAnnotationMissing, + ErrorRunAsNonRootPSCNilCSCNil, ErrorAppArmorAnnotationMissing, ErrorSeccompAnnotationMissing, } runAuditTest(t, "audit_all_v1beta1.yml", mergeAuditFunctions(allAuditFunctions), requiredErrors) } diff --git a/cmd/autofix.go b/cmd/autofix.go index 06bcc521..362c2ee5 100644 --- a/cmd/autofix.go +++ b/cmd/autofix.go @@ -27,7 +27,7 @@ func fixPotentialSecurityIssue(resource Resource, result Result) Resource { resource = fixPrivileged(resource, occurrence) case ErrorReadOnlyRootFilesystemFalse, ErrorReadOnlyRootFilesystemNil: resource = fixReadOnlyRootFilesystem(resource, occurrence) - case ErrorRunAsNonRootFalse, ErrorRunAsNonRootNil: + case ErrorRunAsNonRootPSCTrueFalseCSCFalse, ErrorRunAsNonRootPSCNilCSCNil: resource = fixRunAsNonRoot(resource, occurrence) case ErrorServiceAccountTokenDeprecated: resource = fixDeprecatedServiceAccount(resource) @@ -46,7 +46,7 @@ func fixPotentialSecurityIssue(resource Resource, result Result) Resource { func prepareResourceForFix(resource Resource, result Result) Resource { needSecurityContextDefined := []int{ErrorAllowPrivilegeEscalationNil, ErrorAllowPrivilegeEscalationTrue, ErrorPrivilegedNil, ErrorPrivilegedTrue, ErrorReadOnlyRootFilesystemFalse, ErrorReadOnlyRootFilesystemNil, - ErrorRunAsNonRootFalse, ErrorRunAsNonRootNil, ErrorServiceAccountTokenDeprecated, + ErrorRunAsNonRootPSCTrueFalseCSCFalse, ErrorRunAsNonRootPSCNilCSCNil, ErrorServiceAccountTokenDeprecated, ErrorAutomountServiceAccountTokenTrueAndNoName, ErrorAutomountServiceAccountTokenNilAndNoName, ErrorCapabilityNotDropped, ErrorCapabilityAdded, ErrorMisconfiguredKubeauditAllow} needCapabilitiesDefined := []int{ErrorCapabilityNotDropped, ErrorCapabilityAdded, ErrorMisconfiguredKubeauditAllow} diff --git a/cmd/errors.go b/cmd/errors.go index c538433c..fec8df9b 100644 --- a/cmd/errors.go +++ b/cmd/errors.go @@ -56,12 +56,14 @@ const ( ErrorResourcesLimitsMemoryNil // ErrorResourcesLimitsNil occurs when the resource limit is set to nil. ErrorResourcesLimitsNil - // ErrorRunAsNonRootFalse occurs when RunAsNonRoot is set to false. - ErrorRunAsNonRootFalse + // ErrorRunAsNonRootPSCTrueCSCFalse occurs when RunAsNonRoot is set to false in the ContainerSecurityContext and to true/false in PodSecurityContext. + ErrorRunAsNonRootPSCTrueFalseCSCFalse + // ErrorRunAsNonRootPSCFalseCSCNil occurs when RunAsNonRoot is Nil in the ContainerSecurityContext and to false in Pod ecurityContext. + ErrorRunAsNonRootPSCFalseCSCNil // ErrorRunAsNonRootFalseAllowed occurs when RunAsNonRoot is allowed to be set to false. ErrorRunAsNonRootFalseAllowed - // ErrorRunAsNonRootNil occurs when RunAsNonRoot is not set. - ErrorRunAsNonRootNil + // ErrorRunAsNonRootNil occurs when RunAsNonRoot is not set in either PodSecurityContext or ContainerSecurityContext. + ErrorRunAsNonRootPSCNilCSCNil // ErrorServiceAccountTokenDeprecated occurs when serviceAccount is used. ServiceAccount is a deprecated alias // for ServiceAccountName. ErrorServiceAccountTokenDeprecated diff --git a/cmd/k8sruntime_util.go b/cmd/k8sruntime_util.go index c14f1571..21db327c 100644 --- a/cmd/k8sruntime_util.go +++ b/cmd/k8sruntime_util.go @@ -199,6 +199,16 @@ func getContainers(resource Resource) (container []ContainerV1) { return container } +// Get PodSpec from the PodV1 resource type to check for PSC + +func getPodSpecs(resource Resource) (podSpec PodSpecV1) { + switch kubeType := resource.(type) { + case *PodV1: + podSpec = kubeType.Spec + } + return podSpec +} + func getPodAnnotations(resource Resource) (annotations map[string]string) { switch kubeType := resource.(type) { case *CronJobV1Beta1: diff --git a/cmd/occurrence.go b/cmd/occurrence.go index 91588a2e..222cc55f 100644 --- a/cmd/occurrence.go +++ b/cmd/occurrence.go @@ -7,4 +7,5 @@ type Occurrence struct { message string // just the message container string // name of the container metadata Metadata + podHost string // Hostname of the pod } diff --git a/cmd/result.go b/cmd/result.go index 4392e473..cea09109 100644 --- a/cmd/result.go +++ b/cmd/result.go @@ -58,7 +58,14 @@ func createFields(res Result, occ Occurrence) (fields log.Fields) { for k, v := range occ.metadata { fields[k] = v } - fields["Container"] = occ.container + if len(occ.container) != 0 { + fields["Container"] = occ.container + } + + if occ.id == ErrorRunAsNonRootPSCFalseCSCNil && len(occ.podHost) != 0 { + fields["Pod"] = occ.podHost + } + return } diff --git a/cmd/runAsNonRoot.go b/cmd/runAsNonRoot.go index 7b0b6a08..994ae809 100644 --- a/cmd/runAsNonRoot.go +++ b/cmd/runAsNonRoot.go @@ -5,14 +5,15 @@ import ( "github.com/spf13/cobra" ) -func checkRunAsNonRoot(container ContainerV1, result *Result) { +// Checks the CSC for RANR +func checkRunAsNonRootCSC(container ContainerV1, result *Result) { if reason := result.Labels["audit.kubernetes.io/allow-run-as-root"]; reason != "" { if container.SecurityContext == nil || container.SecurityContext.RunAsNonRoot == nil || *container.SecurityContext.RunAsNonRoot == false { occ := Occurrence{ container: container.Name, id: ErrorRunAsNonRootFalseAllowed, kind: Warn, - message: "Allowed setting RunAsNonRoot to false", + message: "Allowed setting RunAsNonRoot to false in ContainerSecurityContext", metadata: Metadata{"Reason": prettifyReason(reason)}, } result.Occurrences = append(result.Occurrences, occ) @@ -29,17 +30,55 @@ func checkRunAsNonRoot(container ContainerV1, result *Result) { } else if container.SecurityContext == nil || container.SecurityContext.RunAsNonRoot == nil { occ := Occurrence{ container: container.Name, - id: ErrorRunAsNonRootNil, + id: ErrorRunAsNonRootPSCNilCSCNil, kind: Error, - message: "RunAsNonRoot is not set, which results in root user being allowed!", + message: "RunAsNonRoot is not set in ContainerSecurityContext, which results in root user being allowed!", } result.Occurrences = append(result.Occurrences, occ) } else if *container.SecurityContext.RunAsNonRoot == false { occ := Occurrence{ container: container.Name, - id: ErrorRunAsNonRootFalse, + id: ErrorRunAsNonRootPSCTrueFalseCSCFalse, kind: Error, - message: "RunAsNonRoot is set to false (root user allowed), please set to true!", + message: "RunAsNonRoot is set to false (root user allowed) in ContainerSecurityContext, please set to true!", + } + result.Occurrences = append(result.Occurrences, occ) + } + return +} + +// Checks the PodSecurityContext for RANR + +func checkRunAsNonRootPSC(podSpec PodSpecV1, result *Result, containerName string) { + if reason := result.Labels["audit.kubernetes.io/allow-run-as-root"]; reason != "" { + if podSpec.SecurityContext == nil || podSpec.SecurityContext.RunAsNonRoot == nil || *podSpec.SecurityContext.RunAsNonRoot == false { + occ := Occurrence{ + container: containerName, + podHost: podSpec.Hostname, + id: ErrorRunAsNonRootFalseAllowed, + kind: Warn, + message: "Allowed setting RunAsNonRoot to false", + metadata: Metadata{"Reason": prettifyReason(reason)}, + } + result.Occurrences = append(result.Occurrences, occ) + } else { + occ := Occurrence{ + container: containerName, + podHost: podSpec.Hostname, + id: ErrorMisconfiguredKubeauditAllow, + kind: Warn, + message: "Allowed setting RunAsNonRoot to false, but it is set to true", + metadata: Metadata{"Reason": prettifyReason(reason)}, + } + result.Occurrences = append(result.Occurrences, occ) + } + } else if *podSpec.SecurityContext.RunAsNonRoot == false { + occ := Occurrence{ + container: containerName, + podHost: podSpec.Hostname, + id: ErrorRunAsNonRootPSCFalseCSCNil, + kind: Error, + message: "RunAsNonRoot is set to false (root user allowed) in PodsSecurityContext and not set in ContainerSecurityContext, please set to true!", } result.Occurrences = append(result.Occurrences, occ) } @@ -47,6 +86,8 @@ func checkRunAsNonRoot(container ContainerV1, result *Result) { } func auditRunAsNonRoot(resource Resource) (results []Result) { + // get PodSpec for PodSecurityContext + podSpec := getPodSpecs(resource) for _, container := range getContainers(resource) { result, err := newResultFromResource(resource) if err != nil { @@ -54,7 +95,12 @@ func auditRunAsNonRoot(resource Resource) (results []Result) { return } - checkRunAsNonRoot(container, result) + // check if ContainerSecurityContext is defined properly, else audit the PodSecurityContext + if shouldAuditCSC(podSpec, container) { + checkRunAsNonRootCSC(container, result) + } else { + checkRunAsNonRootPSC(podSpec, result, container.Name) + } if len(result.Occurrences) > 0 { results = append(results, *result) } diff --git a/cmd/runAsNonRoot_test.go b/cmd/runAsNonRoot_test.go index 7d771660..518d94b2 100644 --- a/cmd/runAsNonRoot_test.go +++ b/cmd/runAsNonRoot_test.go @@ -5,15 +5,15 @@ import ( ) func TestSecurityContextNilV1(t *testing.T) { - runAuditTest(t, "security_context_nil_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootNil}) + runAuditTest(t, "security_context_nil_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootPSCNilCSCNil}) } func TestRunAsNonRootNilV1(t *testing.T) { - runAuditTest(t, "run_as_non_root_nil_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootNil}) + runAuditTest(t, "run_as_non_root_nil_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootPSCNilCSCNil}) } func TestRunAsNonRootFalseV1(t *testing.T) { - runAuditTest(t, "run_as_non_root_false_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootFalse}) + runAuditTest(t, "run_as_non_root_false_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootPSCTrueFalseCSCFalse}) } func TestRunAsRootFalseAllowedV1(t *testing.T) { @@ -23,3 +23,30 @@ func TestRunAsRootFalseAllowedV1(t *testing.T) { func TestRunAsNonRootMisconfiguredAllowV1(t *testing.T) { runAuditTest(t, "run_as_non_root_misconfigured_allow_v1.yml", auditRunAsNonRoot, []int{ErrorMisconfiguredKubeauditAllow}) } + +func TestPSCFalseCSCNilRunAsNonRootV1(t *testing.T) { + runAuditTest(t, "run_as_non_root_psc_false_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootPSCFalseCSCNil}) +} + +func TestPSCTrueCSCFalseRunAsNonRootFalseV1(t *testing.T) { + runAuditTest(t, "run_as_non_root_psc_true_csc_false_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootPSCTrueFalseCSCFalse}) +} + +func TestPSCFalseCSCFalseRunAsNonRootFalseV1(t *testing.T) { + runAuditTest(t, "run_as_non_root_psc_false_csc_false_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootPSCTrueFalseCSCFalse}) +} +func TestPSCRunAsRootFalseAllowedV1(t *testing.T) { + runAuditTest(t, "run_as_non_root_psc_false_allowed_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootFalseAllowed}) +} + +func TestPSCFalseCSCTrueRunAsNonRootFalseV1(t *testing.T) { + runAuditTest(t, "run_as_non_root_psc_false_csc_true_v1.yml", auditRunAsNonRoot, []int{}) +} + +func TestPSCFalseCSCNilMultipleRunAsNonRootFalseV1(t *testing.T) { + runAuditTest(t, "run_as_non_root_psc_false_csc_nil_multiple_cont_v1.yml", auditRunAsNonRoot, []int{ErrorRunAsNonRootPSCFalseCSCNil}) +} + +func TestPSCFalseCSCTrueMultipleRunAsNonRootFalseV1(t *testing.T) { + runAuditTest(t, "run_as_non_root_psc_false_csc_true_multiple_cont_v1.yml", auditRunAsNonRoot, []int{}) +} diff --git a/cmd/util.go b/cmd/util.go index 2bd27319..d0a0dbb2 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -360,3 +360,13 @@ func prettifyReason(reason string) string { } return reason } + +func shouldAuditCSC(podSpec PodSpecV1, container ContainerV1) bool { + if container.SecurityContext != nil && container.SecurityContext.RunAsNonRoot != nil { + return true + } + if podSpec.SecurityContext == nil || podSpec.SecurityContext.RunAsNonRoot == nil { + return true + } + return false +} diff --git a/fixtures/run_as_non_root_psc_false_allowed_v1.yml b/fixtures/run_as_non_root_psc_false_allowed_v1.yml new file mode 100644 index 00000000..5b351df1 --- /dev/null +++ b/fixtures/run_as_non_root_psc_false_allowed_v1.yml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: run_as_non_root_false_allowed + labels: + apps: fakeSecurityContext + audit.kubernetes.io/allow-run-as-root: "Superuser privileges needed" + namespace: fakeDeploymentRANR +spec: + securityContext: + runAsNonRoot: false + containers: + - name: fakeContainerRANR + resources: {} + securityContext: {} +status: {} diff --git a/fixtures/run_as_non_root_psc_false_csc_false_v1.yml b/fixtures/run_as_non_root_psc_false_csc_false_v1.yml new file mode 100644 index 00000000..c90231db --- /dev/null +++ b/fixtures/run_as_non_root_psc_false_csc_false_v1.yml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: run_as_non_root_psc_false_csc_false + namespace: fakeDeploymentRANR +spec: + securityContext: + runAsNonRoot: false + containers: + - name: fakeContainerRANR + resources: {} + securityContext: + runAsNonRoot: false +status: {} diff --git a/fixtures/run_as_non_root_psc_false_csc_nil_multiple_cont_v1.yml b/fixtures/run_as_non_root_psc_false_csc_nil_multiple_cont_v1.yml new file mode 100644 index 00000000..30a5c8f2 --- /dev/null +++ b/fixtures/run_as_non_root_psc_false_csc_nil_multiple_cont_v1.yml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: run_as_non_root_psc_false_csc_nil_multiple + namespace: fakeDeploymentRANR +spec: + hostname: multiple_containers + securityContext: + runAsNonRoot: false + containers: + - name: fakeContainerRANR1 + resources: {} + securityContext: + runAsNonRoot: true + - name: fakeContainerRANR2 + resources: {} + securityContext: {} +status: {} diff --git a/fixtures/run_as_non_root_psc_false_csc_true_multiple_cont_v1.yml b/fixtures/run_as_non_root_psc_false_csc_true_multiple_cont_v1.yml new file mode 100644 index 00000000..5a87d013 --- /dev/null +++ b/fixtures/run_as_non_root_psc_false_csc_true_multiple_cont_v1.yml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: run_as_non_root_psc_false_csc_true_multiple + namespace: fakeDeploymentRANR +spec: + hostname: multiple_containers + securityContext: + runAsNonRoot: false + containers: + - name: fakeContainerRANR1 + resources: {} + securityContext: + runAsNonRoot: true + - name: fakeContainerRANR2 + resources: {} + securityContext: + runAsNonRoot: true +status: {} diff --git a/fixtures/run_as_non_root_psc_false_csc_true_v1.yml b/fixtures/run_as_non_root_psc_false_csc_true_v1.yml new file mode 100644 index 00000000..7d6040fa --- /dev/null +++ b/fixtures/run_as_non_root_psc_false_csc_true_v1.yml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: run_as_non_root_psc_false_csc_false + namespace: fakeDeploymentRANR +spec: + securityContext: + runAsNonRoot: false + containers: + - name: fakeContainerRANR + resources: {} + securityContext: + runAsNonRoot: true +status: {} diff --git a/fixtures/run_as_non_root_psc_false_v1.yml b/fixtures/run_as_non_root_psc_false_v1.yml new file mode 100644 index 00000000..ef1c8491 --- /dev/null +++ b/fixtures/run_as_non_root_psc_false_v1.yml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: run_as_non_root_false + labels: + apps: fakeSecurityContext + namespace: fakeDeploymentRANR +spec: + hostname: fakeRANR + securityContext: + runAsNonRoot: false + containers: + - name: fakeContainerRANR + resources: {} + securityContext: {} +status: {} diff --git a/fixtures/run_as_non_root_psc_true_csc_false_v1.yml b/fixtures/run_as_non_root_psc_true_csc_false_v1.yml new file mode 100644 index 00000000..9469077d --- /dev/null +++ b/fixtures/run_as_non_root_psc_true_csc_false_v1.yml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: run_as_non_root_psc_true_csc_false + namespace: fakeDeploymentRANR +spec: + securityContext: + runAsNonRoot: true + containers: + - name: fakeContainerRANR + resources: {} + securityContext: + runAsNonRoot: false +status: {}