Repository: FairwindsOps/polaris
Pull request #816 (2022-07-29 15:56:45)

FWI-2476: Add missingNetworkPolicy, automountServiceAccountToken, and linuxHardening checks
## Checklist
* [x] I have signed the CLA
* [x] I have updated/added any relevant documentation

## Description
### What's the goal of this PR?
Add checks for

* missingNetworkPolicy - requires a NetworkPolicy that contains applied egress and ingress rules, and targets a Pod label.
* automountServiceAccountToken - requires that auto mounting of a ServiceAccount token is disabled for a Pod, either at the ServiceAccount or Pod level.
* linuxHardening - requires that one of seLinux options, AppArmor, sec comp profile, or dropping Linux capabilities are used to prevent a container from using unwanted privileges. Some of these can be set at the Pod or Container level.
### What changes did you make?
Added the above checks along with success and failure schema test-cases to validate fields which can be set or overridden at the pod and container spec.

Internal Ticket: [FWI-2476](https://fairwinds.myjetbrains.com/youtrack/issue/FWI-2476)

fairwinds-insights[bot]: ## Fairwinds Insights CI Report
> [View the Full Report](https://insights.fairwinds.com/gh/FairwindsOps/polaris/if%2FFWI-2476-additional_checks)

:white_check_mark: No new Action Items detected!
fairwinds-insights[bot]: ## Fairwinds Insights CI Report
> [View the Full Report](https://insights.fairwinds.com/gh/FairwindsOps/polaris/if%2FFWI-2476-additional_checks)

:white_check_mark: No new Action Items detected!
ivanfetch-fw: Barnie [confirmed he doesn't have additional comments and this can be merged](https://fairwindsops.slack.com/archives/CNQBGRGFN/p1659714043413909?thread_ts=1658858841.999019&cid=CNQBGRGFN).

Patch:
diff --git a/checks/automountServiceAccountToken.yaml b/checks/automountServiceAccountToken.yaml
new file mode 100644
index 00000000..69cc01c5
--- /dev/null
+++ b/checks/automountServiceAccountToken.yaml
@@ -0,0 +1,36 @@
+successMessage: The ServiceAccount will not be automounted
+failureMessage: The ServiceAccount will be automounted
+category: Security
+target: PodSpec
+schema:
+  '$schema': http://json-schema.org/draft-07/schema
+  type: object
+  required: ["serviceAccountName"]
+  properties:
+    serviceAccountName:
+      type: string
+    automountServiceAccountToken:
+      type: boolean
+      not:
+        const: true
+additionalSchemaStrings:
+  ServiceAccount: |
+    type: object
+    required:
+      - metadata
+      {{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
+      - automountServiceAccountToken
+      {{ end }}
+    properties:
+      metadata:
+        type: object
+        required: ["name"]
+        properties:
+          name:
+            type: string
+            const: "{{ .Polaris.PodSpec.serviceAccountName }}"
+      {{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
+      automountServiceAccountToken:
+        type: boolean
+        const: false
+      {{ end }}
diff --git a/checks/linuxHardening.yaml b/checks/linuxHardening.yaml
new file mode 100644
index 00000000..cbb64270
--- /dev/null
+++ b/checks/linuxHardening.yaml
@@ -0,0 +1,89 @@
+successMessage: One of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities are used to restrict containers using unwanted privileges
+FailureMessage: Use one of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities to restrict containers using unwanted privileges
+category: Security
+target: Container
+schemaString: |
+  '$schema': http://json-schema.org/draft-07/schema
+  definitions:
+    podOrContainerSeccompProfile:
+      type: object
+      {{ $podSeccompProfileType := .Polaris.PodSpec.securityContext.seccompProfile.type }}
+      required:
+      {{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
+        - securityContext
+      {{ end }}
+      properties:
+        securityContext:
+          type: object
+          required:
+          {{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
+            - seccompProfile
+          {{ end }}
+          properties:
+            seccompProfile:
+              type: object
+              required:
+              {{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
+                - type
+              {{ end }}
+              properties:
+                type:
+                  type: string
+                  allOf:
+                    - not:
+                        const: "Unconfined"
+                    {{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
+                    - minLength: 1
+                    {{ end }}
+    podOrContainerSELinuxOptions:
+      type: object
+      {{ $podSELinuxOptions := .Polaris.PodSpec.securityContext.seLinuxOptions }}
+      {{ if not $podSELinuxOptions }}
+      required: ["securityContext"]
+      properties:
+        securityContext:
+          type: object
+          required: ["seLinuxOptions"]
+          properties:
+            seLinuxOptions:
+              type: object
+              minProperties: 1
+      {{ end }}
+    containerDropCapabilities:
+      type: object
+      required: ["securityContext"]
+      properties:
+        securityContext:
+          type: object
+          required: ["capabilities"]
+          properties:
+            capabilities:
+              type: object
+              required: ["drop"]
+              properties:
+                drop:
+                  type: array
+                  minItems: 1
+                add:
+                  type: array
+                  items:
+                    type: string
+                    not:
+                      pattern: '^(?i)ALL$'
+  # End of definitions
+  {{/* Check for AppArmor which uses pod annotations. IF pod fields are missing,
+       require one of the other hardening measures. */}}
+  {{ $annotationName := (print "container.apparmor.security.beta.kubernetes.io/" .Polaris.Container.name) }}
+  {{/* Checking annotations before using index() avoids a nil panic when there are no annotations */}}
+  {{ $annotationExists := false }}
+  {{ if .Polaris.PodTemplate.metadata.annotations }}
+  {{ $annotationExists = index .Polaris "PodTemplate" "metadata" "annotations" $annotationName }}
+  {{ end }}
+  {{ if $annotationExists }}
+  type: object
+  {{ else }}
+  anyOf:
+  - $ref: "#/definitions/podOrContainerSeccompProfile"
+  - $ref: "#/definitions/podOrContainerSELinuxOptions"
+  - $ref: "#/definitions/containerDropCapabilities"
+  {{ end}}
diff --git a/checks/missingNetworkPolicy.yaml b/checks/missingNetworkPolicy.yaml
new file mode 100644
index 00000000..b349f87e
--- /dev/null
+++ b/checks/missingNetworkPolicy.yaml
@@ -0,0 +1,49 @@
+successMessage: A NetworkPolicy matches pod labels and contains egress and ingress rules
+failureMessage: A NetworkPolicy should match pod labels and contain applied egress and ingress rules
+category: Security
+target: PodTemplate
+schema:
+  '$schema': http://json-schema.org/draft-07/schema
+  type: object
+  properties:
+    metadata:
+      type: object
+      properties:
+        labels:
+          type: object
+          minProperties: 1
+additionalSchemaStrings:
+  networking.k8s.io/NetworkPolicy: |
+    type: object
+    properties:
+      spec:
+        type: object
+        required: ["podSelector", "egress", "ingress"]
+        properties:
+          podSelector:
+            type: object
+            required: ["matchLabels"]
+            properties:
+              matchLabels:
+                type: object
+                oneOf:
+                {{ range $key, $value := .Polaris.PodTemplate.metadata.labels }}
+                - properties:
+                    "{{ $key }}":
+                      type: string
+                      const: {{ $value }}
+                  required: ["{{ $key }}"]
+                {{ end }}
+          egress:
+            type: array
+            minItems: 1
+          ingress:
+            minItems: 1
+            type: array
+          policyTypes:
+            type: array
+            allOf:
+              - contains:
+                  pattern: '^(?i)Egress$'
+              - contains:
+                  pattern: '^(?i)Ingress$'
diff --git a/examples/config-full.yaml b/examples/config-full.yaml
index 11613cdd..4b992c26 100644
--- a/examples/config-full.yaml
+++ b/examples/config-full.yaml
@@ -12,8 +12,11 @@ checks:
   memoryRequestsMissing: warning
   memoryLimitsMissing: warning
   # security
+  automountServiceAccountToken: warning
   hostIPCSet: danger
   hostPIDSet: danger
+  linuxHardening: danger
+  missingNetworkPolicy: warning
   notReadOnlyRootFilesystem: warning
   privilegeEscalationAllowed: danger
   runAsRootAllowed: danger
diff --git a/pkg/config/checks.go b/pkg/config/checks.go
index 48ce4f62..15500cad 100644
--- a/pkg/config/checks.go
+++ b/pkg/config/checks.go
@@ -32,6 +32,7 @@ var (
 		"hostIPCSet",
 		"hostPIDSet",
 		"hostNetworkSet",
+		"automountServiceAccountToken",
 		// Container checks
 		"memoryLimitsMissing",
 		"memoryRequestsMissing",
@@ -49,11 +50,13 @@ var (
 		"dangerousCapabilities",
 		"insecureCapabilities",
 		"priorityClassNotSet",
+		"linuxHardening",
 		// Other checks
 		"tlsSettingsMissing",
 		"pdbDisruptionsIsZero",
 		"metadataAndNameMismatched",
 		"missingPodDisruptionBudget",
+		"missingNetworkPolicy",
 	}
 )
 


Test patch:
diff --git a/test/checks/automountServiceAccountToken/failure.all_true.yaml b/test/checks/automountServiceAccountToken/failure.all_true.yaml
new file mode 100644
index 00000000..5e4383c2
--- /dev/null
+++ b/test/checks/automountServiceAccountToken/failure.all_true.yaml
@@ -0,0 +1,19 @@
+# This fails because automounting is true for both the pod and ServiceAccount.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  automountServiceAccountToken: true
+  serviceAccountName: test
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: test
+automountServiceAccountToken: true
diff --git a/test/checks/automountServiceAccountToken/failure.pod_true_serviceaccount_false.yaml b/test/checks/automountServiceAccountToken/failure.pod_true_serviceaccount_false.yaml
new file mode 100644
index 00000000..aeb2d17f
--- /dev/null
+++ b/test/checks/automountServiceAccountToken/failure.pod_true_serviceaccount_false.yaml
@@ -0,0 +1,19 @@
+# This fails because automounting is true for the pod, overriding the ServiceAccount.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  automountServiceAccountToken: true
+  serviceAccountName: test
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: test
+automountServiceAccountToken: false
diff --git a/test/checks/automountServiceAccountToken/failure.uses_defaults.yaml b/test/checks/automountServiceAccountToken/failure.uses_defaults.yaml
new file mode 100644
index 00000000..57a63e4d
--- /dev/null
+++ b/test/checks/automountServiceAccountToken/failure.uses_defaults.yaml
@@ -0,0 +1,17 @@
+# This fails because automounting is not disabled anywhere.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  serviceAccountName: test
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: test
diff --git a/test/checks/automountServiceAccountToken/success.pod.yaml b/test/checks/automountServiceAccountToken/success.pod.yaml
new file mode 100644
index 00000000..1da4dc6a
--- /dev/null
+++ b/test/checks/automountServiceAccountToken/success.pod.yaml
@@ -0,0 +1,19 @@
+# This succeeds because automounting is disabled at the pod.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  automountServiceAccountToken: false
+  serviceAccountName: test
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: test
+
diff --git a/test/checks/automountServiceAccountToken/success.pod_and_serviceaccount.yaml b/test/checks/automountServiceAccountToken/success.pod_and_serviceaccount.yaml
new file mode 100644
index 00000000..6b13be49
--- /dev/null
+++ b/test/checks/automountServiceAccountToken/success.pod_and_serviceaccount.yaml
@@ -0,0 +1,19 @@
+# This succeeds because automounting is disabled at the pod and ServiceAccount.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  automountServiceAccountToken: false
+  serviceAccountName: test
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: test
+automountServiceAccountToken: false
diff --git a/test/checks/automountServiceAccountToken/success.pod_false_serviceaccount_true.yaml b/test/checks/automountServiceAccountToken/success.pod_false_serviceaccount_true.yaml
new file mode 100644
index 00000000..db568283
--- /dev/null
+++ b/test/checks/automountServiceAccountToken/success.pod_false_serviceaccount_true.yaml
@@ -0,0 +1,19 @@
+# This succeeds because automounting is disabled at the pod, overriding the ServiceAccount.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  automountServiceAccountToken: false
+  serviceAccountName: test
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: test
+automountServiceAccountToken: true
diff --git a/test/checks/automountServiceAccountToken/success.serviceaccount.yaml b/test/checks/automountServiceAccountToken/success.serviceaccount.yaml
new file mode 100644
index 00000000..509c9c7d
--- /dev/null
+++ b/test/checks/automountServiceAccountToken/success.serviceaccount.yaml
@@ -0,0 +1,18 @@
+# This succeeds because automounting is disabled at the ServiceAccount.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  serviceAccountName: test
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: test
+automountServiceAccountToken: false
diff --git a/test/checks/linuxHardening/failure.apparmor_incorrect_annotation_names.yaml b/test/checks/linuxHardening/failure.apparmor_incorrect_annotation_names.yaml
new file mode 100644
index 00000000..526f994a
--- /dev/null
+++ b/test/checks/linuxHardening/failure.apparmor_incorrect_annotation_names.yaml
@@ -0,0 +1,18 @@
+# This fails because the annotation names do not match the containers.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  annotations:
+    container.apparmor.security.beta.kubernetes.io/container1: runtime/default
+    container.apparmor.security.beta.kubernetes.io/container2: runtime/default
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+  - name: nginx2
+    image: nginx
+    ports:
+    - containerPort: 81
diff --git a/test/checks/linuxHardening/failure.drop_capabilities_but_add_all.yaml b/test/checks/linuxHardening/failure.drop_capabilities_but_add_all.yaml
new file mode 100644
index 00000000..566de74c
--- /dev/null
+++ b/test/checks/linuxHardening/failure.drop_capabilities_but_add_all.yaml
@@ -0,0 +1,17 @@
+# This fails because ALL capabilities are also added.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    securityContext:
+      capabilities:
+        drop:
+          - ALL
+        add:
+          - all
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/failure.nothing_is_satisfied.yaml b/test/checks/linuxHardening/failure.nothing_is_satisfied.yaml
new file mode 100644
index 00000000..8e56f603
--- /dev/null
+++ b/test/checks/linuxHardening/failure.nothing_is_satisfied.yaml
@@ -0,0 +1,11 @@
+# This fails because none of the seccompProfile, seLinuxOptions, AppArmor, or dropping capabilities are present.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/failure.seccomp_profile_container_overrides_pod.yaml b/test/checks/linuxHardening/failure.seccomp_profile_container_overrides_pod.yaml
new file mode 100644
index 00000000..f47520b6
--- /dev/null
+++ b/test/checks/linuxHardening/failure.seccomp_profile_container_overrides_pod.yaml
@@ -0,0 +1,17 @@
+# This fails because the container overrides the pod profile type.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  securityContext:
+    seccompProfile:
+      type: RuntimeDefault
+  containers:
+  - name: nginx
+    securityContext:
+      seccompProfile:
+        type: Unconfined
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/failure.seccomp_profile_pod_missing_type.yaml b/test/checks/linuxHardening/failure.seccomp_profile_pod_missing_type.yaml
new file mode 100644
index 00000000..fd2c8e8e
--- /dev/null
+++ b/test/checks/linuxHardening/failure.seccomp_profile_pod_missing_type.yaml
@@ -0,0 +1,13 @@
+# This fails because the pod seccompPRofile is missing the type field.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  securityContext:
+    seccompProfile:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/failure.seccomp_profile_pod_unconfined.yaml b/test/checks/linuxHardening/failure.seccomp_profile_pod_unconfined.yaml
new file mode 100644
index 00000000..7415ae41
--- /dev/null
+++ b/test/checks/linuxHardening/failure.seccomp_profile_pod_unconfined.yaml
@@ -0,0 +1,14 @@
+# This fails because the container overrides the pod profile type.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  securityContext:
+    seccompProfile:
+      type: Unconfined
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/failure.selinux_container_missing_subfield.yaml b/test/checks/linuxHardening/failure.selinux_container_missing_subfield.yaml
new file mode 100644
index 00000000..ffc6869b
--- /dev/null
+++ b/test/checks/linuxHardening/failure.selinux_container_missing_subfield.yaml
@@ -0,0 +1,13 @@
+# This fails because seLinuxOptions is defined but without any sub-field.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  containers:
+  - name: nginx
+    securityContext:
+      seLinuxOptions:
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/failure.selinux_pod_missing_subfield.yaml b/test/checks/linuxHardening/failure.selinux_pod_missing_subfield.yaml
new file mode 100644
index 00000000..884dbbb1
--- /dev/null
+++ b/test/checks/linuxHardening/failure.selinux_pod_missing_subfield.yaml
@@ -0,0 +1,13 @@
+# This fails because seLinuxOptions is defined but without any sub-field.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  securityContext:
+    seLinuxOptions:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/success.apparmor_2_containers.yaml b/test/checks/linuxHardening/success.apparmor_2_containers.yaml
new file mode 100644
index 00000000..88fd15c3
--- /dev/null
+++ b/test/checks/linuxHardening/success.apparmor_2_containers.yaml
@@ -0,0 +1,18 @@
+# This succeeds because AppArmor annotations exist matching both container names.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  annotations:
+    container.apparmor.security.beta.kubernetes.io/nginx: runtime/default
+    container.apparmor.security.beta.kubernetes.io/nginx2: runtime/default
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+  - name: nginx2
+    image: nginx
+    ports:
+    - containerPort: 81
diff --git a/test/checks/linuxHardening/success.drop_capabilities.yaml b/test/checks/linuxHardening/success.drop_capabilities.yaml
new file mode 100644
index 00000000..5b6a0446
--- /dev/null
+++ b/test/checks/linuxHardening/success.drop_capabilities.yaml
@@ -0,0 +1,17 @@
+# This succeeds because SOME capability is dropped, and ALL capabilities are not added.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    securityContext:
+      capabilities:
+        drop:
+        - ALL
+        add:
+        - someCapabilities
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/success.seccomp_profile_container.yaml b/test/checks/linuxHardening/success.seccomp_profile_container.yaml
new file mode 100644
index 00000000..388722f8
--- /dev/null
+++ b/test/checks/linuxHardening/success.seccomp_profile_container.yaml
@@ -0,0 +1,14 @@
+# This succeeds because a seccomp profile is defined for the container.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  containers:
+  - name: nginx
+    securityContext:
+      seccompProfile:
+        type: RuntimeDefault
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/success.seccomp_profile_pod.yaml b/test/checks/linuxHardening/success.seccomp_profile_pod.yaml
new file mode 100644
index 00000000..e9b62ff9
--- /dev/null
+++ b/test/checks/linuxHardening/success.seccomp_profile_pod.yaml
@@ -0,0 +1,14 @@
+# This succeeds because a seccomp profile is defined for the pod and not undefined for the container.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  securityContext:
+    seccompProfile:
+      type: RuntimeDefault
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/success.seccomp_profile_unconfined_for_pod_but_set_for_container.yaml b/test/checks/linuxHardening/success.seccomp_profile_unconfined_for_pod_but_set_for_container.yaml
new file mode 100644
index 00000000..b1f3e136
--- /dev/null
+++ b/test/checks/linuxHardening/success.seccomp_profile_unconfined_for_pod_but_set_for_container.yaml
@@ -0,0 +1,17 @@
+# This succeeds because the container seccomp profile  overrides the pod Unconfined setting.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  securityContext:
+    seccompProfile:
+      type: Unconfined
+  containers:
+  - name: nginx
+    securityContext:
+      seccompProfile:
+        type: RuntimeDefault
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/success.selinux_container.yaml b/test/checks/linuxHardening/success.selinux_container.yaml
new file mode 100644
index 00000000..0eb7af55
--- /dev/null
+++ b/test/checks/linuxHardening/success.selinux_container.yaml
@@ -0,0 +1,14 @@
+# This succeeds because seLinuxOptions are defined for the container.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  containers:
+  - name: nginx
+    securityContext:
+      seLinuxOptions:
+        level:   "s0:c123,c456"
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/linuxHardening/success.selinux_pod.yaml b/test/checks/linuxHardening/success.selinux_pod.yaml
new file mode 100644
index 00000000..660f3d0e
--- /dev/null
+++ b/test/checks/linuxHardening/success.selinux_pod.yaml
@@ -0,0 +1,14 @@
+# This succeeds because seLinuxOptions is defined for the pod.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+spec:
+  securityContext:
+    seLinuxOptions:
+      level:   "s0:c123,c456"
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
diff --git a/test/checks/missingNetworkPolicy/failure.netpol_missing_egress_in_policytypes.yaml b/test/checks/missingNetworkPolicy/failure.netpol_missing_egress_in_policytypes.yaml
new file mode 100644
index 00000000..21470991
--- /dev/null
+++ b/test/checks/missingNetworkPolicy/failure.netpol_missing_egress_in_policytypes.yaml
@@ -0,0 +1,41 @@
+# This failes because the NetworkPolicy `policyTypes` lacks `Egress`,
+# without which the egress rules will not be applied.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  labels:
+    security: medium
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: test
+spec:
+  podSelector:
+    matchLabels:
+      security: medium
+  policyTypes:
+  - Ingress
+  ingress:
+  - from:
+    - ipBlock:
+        cidr: 0.0.0.0/0
+    ports:
+    - protocol: TCP
+      port: 8080
+  egress:
+  - to:
+    - podSelector:
+        matchLabels:
+          # Allow outbound with other medium-security pods.
+          security: medium
+    ports:
+    - protocol: TCP
+      port: 80
diff --git a/test/checks/missingNetworkPolicy/failure.netpol_missing_egress_rules.yaml b/test/checks/missingNetworkPolicy/failure.netpol_missing_egress_rules.yaml
new file mode 100644
index 00000000..01f78b82
--- /dev/null
+++ b/test/checks/missingNetworkPolicy/failure.netpol_missing_egress_rules.yaml
@@ -0,0 +1,32 @@
+# This fails because the NetworkPolicy lacks egress rules.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  labels:
+    security: medium
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: test
+spec:
+  podSelector:
+    matchLabels:
+      security: medium
+  policyTypes:
+  - Egress
+  - Ingress
+  ingress:
+  - from:
+    - ipBlock:
+        cidr: 0.0.0.0/0
+    ports:
+    - protocol: TCP
+      port: 8080
diff --git a/test/checks/missingNetworkPolicy/failure.netpol_missing_ingress_in_policytypes.yaml b/test/checks/missingNetworkPolicy/failure.netpol_missing_ingress_in_policytypes.yaml
new file mode 100644
index 00000000..99fdea1f
--- /dev/null
+++ b/test/checks/missingNetworkPolicy/failure.netpol_missing_ingress_in_policytypes.yaml
@@ -0,0 +1,41 @@
+# This failes because the NetworkPolicy `policyTypes` lacks `Ingress`,
+# without which the egress rules will not be applied.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  labels:
+    security: medium
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: test
+spec:
+  podSelector:
+    matchLabels:
+      security: medium
+  policyTypes:
+  - Egress
+  ingress:
+  - from:
+    - ipBlock:
+        cidr: 0.0.0.0/0
+    ports:
+    - protocol: TCP
+      port: 8080
+  egress:
+  - to:
+    - podSelector:
+        matchLabels:
+          # Allow outbound with other medium-security pods.
+          security: medium
+    ports:
+    - protocol: TCP
+      port: 80
diff --git a/test/checks/missingNetworkPolicy/failure.netpol_missing_ingress_rules.yaml b/test/checks/missingNetworkPolicy/failure.netpol_missing_ingress_rules.yaml
new file mode 100644
index 00000000..4d494ee7
--- /dev/null
+++ b/test/checks/missingNetworkPolicy/failure.netpol_missing_ingress_rules.yaml
@@ -0,0 +1,34 @@
+# This fails because the NetworkPolicy lacks ingress rules.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  labels:
+    security: medium
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: test
+spec:
+  podSelector:
+    matchLabels:
+      security: medium
+  policyTypes:
+  - Egress
+  - Ingress
+  egress:
+  - to:
+    - podSelector:
+        matchLabels:
+          # Allow outbound with other medium-security pods.
+          security: medium
+    ports:
+    - protocol: TCP
+      port: 80
diff --git a/test/checks/missingNetworkPolicy/failure.netpol_targets_wrong_pod_label.yaml b/test/checks/missingNetworkPolicy/failure.netpol_targets_wrong_pod_label.yaml
new file mode 100644
index 00000000..389d3055
--- /dev/null
+++ b/test/checks/missingNetworkPolicy/failure.netpol_targets_wrong_pod_label.yaml
@@ -0,0 +1,41 @@
+# This fails because the NetworkPolicy matches a label the pod does not have
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  labels:
+    security: medium
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: test
+spec:
+  podSelector:
+    matchLabels:
+      security: high
+  policyTypes:
+  - Egress
+  - Ingress
+  ingress:
+  - from:
+    - ipBlock:
+        cidr: 0.0.0.0/0
+    ports:
+    - protocol: TCP
+      port: 8080
+  egress:
+  - to:
+    - podSelector:
+        matchLabels:
+          # Allow outbound with other medium-security pods.
+          security: medium
+    ports:
+    - protocol: TCP
+      port: 80
diff --git a/test/checks/missingNetworkPolicy/success.yaml b/test/checks/missingNetworkPolicy/success.yaml
new file mode 100644
index 00000000..27d56ea2
--- /dev/null
+++ b/test/checks/missingNetworkPolicy/success.yaml
@@ -0,0 +1,41 @@
+# This succeeds because the NetworkPolicy contains and enables egress and ingress rules, and targets the pod's label.
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-pod
+  labels:
+    security: medium
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: test
+spec:
+  podSelector:
+    matchLabels:
+      security: medium
+  policyTypes:
+  - Egress
+  - Ingress
+  ingress:
+  - from:
+    - ipBlock:
+        cidr: 0.0.0.0/0
+    ports:
+    - protocol: TCP
+      port: 8080
+  egress:
+  - to:
+    - podSelector:
+        matchLabels:
+          # Allow outbound with other medium-security pods.
+          security: medium
+    ports:
+    - protocol: TCP
+      port: 80
diff --git a/test/fixtures.go b/test/fixtures.go
index b10be572..aec01dcd 100644
--- a/test/fixtures.go
+++ b/test/fixtures.go
@@ -199,6 +199,7 @@ func SetupTestAPI(objects ...runtime.Object) (kubernetes.Interface, dynamic.Inte
 			APIResources: []metav1.APIResource{
 				{Name: "pods", Namespaced: true, Kind: "Pod"},
 				{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
+				{Name: "serviceaccounts", Namespaced: true, Kind: "ServiceAccount"},
 			},
 		},
 		{
@@ -239,6 +240,7 @@ func SetupTestAPI(objects ...runtime.Object) (kubernetes.Interface, dynamic.Inte
 			GroupVersion: "networking.k8s.io/v1",
 			APIResources: []metav1.APIResource{
 				{Name: "ingresses", Namespaced: true, Kind: "Ingress", Version: "v1"},
+				{Name: "networkpolicies", Namespaced: true, Kind: "NetworkPolicy", Version: "v1"},
 			},
 		},
 		{