diff --git a/pkg/config/checks.go b/pkg/config/checks.go index 9707d8457..0888755ab 100644 --- a/pkg/config/checks.go +++ b/pkg/config/checks.go @@ -29,11 +29,14 @@ var ( "deploymentMissingReplicas", // Pod checks "hostIPCSet", + "hostPathSet", + "hostProcess", "hostPIDSet", "hostNetworkSet", "automountServiceAccountToken", "topologySpreadConstraint", // Container checks + "procMount", "memoryLimitsMissing", "memoryRequestsMissing", "cpuLimitsMissing", diff --git a/pkg/config/checks/hostPathSet.yaml b/pkg/config/checks/hostPathSet.yaml new file mode 100644 index 000000000..62a4483d6 --- /dev/null +++ b/pkg/config/checks/hostPathSet.yaml @@ -0,0 +1,16 @@ +successMessage: HostPath volumes are not configured +failureMessage: HostPath volumes must be forbidden +category: Security +target: PodSpec +schema: + '$schema': http://json-schema.org/draft-07/schema + type: object + properties: + volumes: + type: array + items: + type: object + properties: + hostPath: + type: string + const: '' diff --git a/pkg/config/checks/hostProcess.yaml b/pkg/config/checks/hostProcess.yaml new file mode 100644 index 000000000..d0cb85d93 --- /dev/null +++ b/pkg/config/checks/hostProcess.yaml @@ -0,0 +1,31 @@ +successMessage: Privileged access to the host check is valid +failureMessage: Privileged access to the host is disallowed +category: Security +target: PodSpec +schema: + '$schema': http://json-schema.org/draft-07/schema + type: object + properties: + containers: + type: array + items: + type: object + properties: + securityContext: + type: object + properties: + windowsOptions: + type: object + properties: + hostProcess: + type: boolean + const: false + securityContext: + type: object + properties: + windowsOptions: + type: object + properties: + hostProcess: + type: boolean + const: false diff --git a/pkg/config/checks/procMount.yaml b/pkg/config/checks/procMount.yaml new file mode 100644 index 000000000..10c547a8a --- /dev/null +++ b/pkg/config/checks/procMount.yaml @@ -0,0 +1,19 @@ +successMessage: The default /proc masks are set up to reduce attack surface, and should be required +failureMessage: Proc mount must not be changed from the default +category: Security +target: PodSpec +schema: + '$schema': http://json-schema.org/draft-07/schema + type: object + properties: + containers: + type: array + items: + type: object + properties: + securityContext: + type: object + properties: + procMount: + type: string + const: Default \ No newline at end of file diff --git a/pkg/config/default.yaml b/pkg/config/default.yaml index 9fc5266ea..b62b358bd 100644 --- a/pkg/config/default.yaml +++ b/pkg/config/default.yaml @@ -23,11 +23,14 @@ checks: # security automountServiceAccountToken: warning hostIPCSet: danger + hostPathSet: warning + hostProcess: warning hostPIDSet: danger linuxHardening: warning missingNetworkPolicy: warning notReadOnlyRootFilesystem: warning privilegeEscalationAllowed: danger + procMount: warning runAsRootAllowed: danger runAsPrivileged: danger dangerousCapabilities: danger diff --git a/pkg/config/examples/config-full.yaml b/pkg/config/examples/config-full.yaml index aa5247c5a..6db56283b 100644 --- a/pkg/config/examples/config-full.yaml +++ b/pkg/config/examples/config-full.yaml @@ -23,11 +23,14 @@ checks: # security automountServiceAccountToken: warning hostIPCSet: danger + hostPathSet: warning + hostProcess: warning hostPIDSet: danger linuxHardening: danger missingNetworkPolicy: warning notReadOnlyRootFilesystem: warning privilegeEscalationAllowed: danger + procMount: warning runAsRootAllowed: danger runAsPrivileged: danger dangerousCapabilities: danger diff --git a/pkg/validator/pod_test.go b/pkg/validator/pod_test.go index 0efc262ed..c1997a5a1 100644 --- a/pkg/validator/pod_test.go +++ b/pkg/validator/pod_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" conf "github.com/fairwindsops/polaris/pkg/config" @@ -32,6 +33,9 @@ func TestValidatePod(t *testing.T) { "hostPIDSet": conf.SeverityDanger, "hostNetworkSet": conf.SeverityWarning, "hostPortSet": conf.SeverityDanger, + "hostPathSet": conf.SeverityWarning, + "procMount": conf.SeverityWarning, + "hostProcess": conf.SeverityWarning, }, } @@ -39,7 +43,7 @@ func TestValidatePod(t *testing.T) { deployment, err := kube.NewGenericResourceFromPod(p, nil) assert.NoError(t, err) expectedSum := CountSummary{ - Successes: uint(4), + Successes: uint(7), Warnings: uint(0), Dangers: uint(0), } @@ -48,6 +52,9 @@ func TestValidatePod(t *testing.T) { "hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Success: true, Severity: "danger", Category: "Security"}, "hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"}, "hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"}, + "hostPathSet": {ID: "hostPathSet", Message: "HostPath volumes are not configured", Success: true, Severity: "warning", Category: "Security"}, + "procMount": {ID: "procMount", Message: "The default /proc masks are set up to reduce attack surface, and should be required", Success: true, Severity: "warning", Category: "Security"}, + "hostProcess": {ID: "hostProcess", Message: "Privileged access to the host check is valid", Success: true, Severity: "warning", Category: "Security"}, } actualPodResult, err := applyControllerSchemaChecks(&c, nil, deployment) @@ -67,22 +74,45 @@ func TestInvalidIPCPod(t *testing.T) { "hostPIDSet": conf.SeverityDanger, "hostNetworkSet": conf.SeverityWarning, "hostPortSet": conf.SeverityDanger, + "hostPathSet": conf.SeverityWarning, + "procMount": conf.SeverityWarning, + "hostProcess": conf.SeverityWarning, }, } p := test.MockPod() p.Spec.HostIPC = true + p.Spec.Volumes = append(p.Spec.Volumes, v1.Volume{ + Name: "hostpath", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/var/run/docker.sock", + }, + }, + }) + procMount := v1.UnmaskedProcMount + p.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ + ProcMount: &procMount, + } + hostProcess := true + p.Spec.Containers[0].SecurityContext.WindowsOptions = &v1.WindowsSecurityContextOptions{ + HostProcess: &hostProcess, + } + workload, err := kube.NewGenericResourceFromPod(p, nil) assert.NoError(t, err) expectedSum := CountSummary{ Successes: uint(3), - Warnings: uint(0), + Warnings: uint(3), Dangers: uint(1), } expectedResults := ResultSet{ "hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC should not be configured", Success: false, Severity: "danger", Category: "Security"}, "hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"}, "hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"}, + "hostPathSet": {ID: "hostPathSet", Message: "HostPath volumes must be forbidden", Success: false, Severity: "warning", Category: "Security"}, + "procMount": {ID: "procMount", Message: "Proc mount must not be changed from the default", Success: false, Severity: "warning", Category: "Security"}, + "hostProcess": {ID: "hostProcess", Message: "Privileged access to the host is disallowed", Success: false, Severity: "warning", Category: "Security"}, } actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload) diff --git a/test/checks/hostPathSet/failure.yaml b/test/checks/hostPathSet/failure.yaml new file mode 100644 index 000000000..86d1ddbf1 --- /dev/null +++ b/test/checks/hostPathSet/failure.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + volumes: + - name: log-volume + hostPath: + path: /var/log diff --git a/test/checks/hostPathSet/success.yaml b/test/checks/hostPathSet/success.yaml new file mode 100644 index 000000000..963ed15a5 --- /dev/null +++ b/test/checks/hostPathSet/success.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + volumes: + - name: log-volume diff --git a/test/checks/hostProcess/failure.container.yaml b/test/checks/hostProcess/failure.container.yaml new file mode 100644 index 000000000..76893fe67 --- /dev/null +++ b/test/checks/hostProcess/failure.container.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 + hostPort: 8080 + securityContext: + windowsOptions: + hostProcess: true diff --git a/test/checks/hostProcess/failure.yaml b/test/checks/hostProcess/failure.yaml new file mode 100644 index 000000000..569bd5251 --- /dev/null +++ b/test/checks/hostProcess/failure.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 + hostPort: 8080 + securityContext: + windowsOptions: + hostProcess: true diff --git a/test/checks/hostProcess/success.container.yaml b/test/checks/hostProcess/success.container.yaml new file mode 100644 index 000000000..b7e4a3927 --- /dev/null +++ b/test/checks/hostProcess/success.container.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 + hostPort: 8080 + securityContext: + windowsOptions: + hostProcess: false diff --git a/test/checks/hostProcess/success.yaml b/test/checks/hostProcess/success.yaml new file mode 100644 index 000000000..0d92c288c --- /dev/null +++ b/test/checks/hostProcess/success.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 + hostPort: 8080 + securityContext: + windowsOptions: + hostProcess: false diff --git a/test/checks/procMount/failure.yaml b/test/checks/procMount/failure.yaml new file mode 100644 index 000000000..30ea3caea --- /dev/null +++ b/test/checks/procMount/failure.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 + hostPort: 8080 + securityContext: + procMount: Other diff --git a/test/checks/procMount/success.yaml b/test/checks/procMount/success.yaml new file mode 100644 index 000000000..8bcb81678 --- /dev/null +++ b/test/checks/procMount/success.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + app.kubernetes.io/name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 + hostPort: 8080 + securityContext: + procMount: Default