├── .travis.yml
├── Makefile
├── test.sh
├── host-namespaces
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── privileged-containers
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── read-only-root-filesystem
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── proc-mount
├── example.yaml
├── constraint.yaml
├── src.rego
├── README.md
├── template.yaml
└── src_test.rego
├── allow-privilege-escalation
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── host-network-ports
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── host-filesystem
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── fsgroup
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── flexvolume-drivers
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── volumes
├── constraint.yaml
├── example.yaml
├── src.rego
├── template.yaml
└── src_test.rego
├── LICENSE
└── README.md
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: generic
2 |
3 | script: make test
4 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: test
2 |
3 | .PHONY: test
4 | test:
5 | sh test.sh
6 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | for path in $PWD/*; do
5 | if [ -d $path ]
6 | then
7 | cd $path
8 | docker run -v $path:/tests openpolicyagent/opa test /tests/src.rego /tests/src_test.rego
9 | fi
10 | done
11 |
--------------------------------------------------------------------------------
/host-namespaces/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPHostNamespace
3 | metadata:
4 | name: psp-host-namespace
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 |
--------------------------------------------------------------------------------
/privileged-containers/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPPrivilegedContainer
3 | metadata:
4 | name: psp-privileged-container
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 |
--------------------------------------------------------------------------------
/read-only-root-filesystem/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPReadOnlyRootFilesystem
3 | metadata:
4 | name: psp-readonlyrootfilesystem
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 |
--------------------------------------------------------------------------------
/host-namespaces/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-host-namespace
5 | labels:
6 | app: nginx-host-namespace
7 | spec:
8 | hostPID: true #false
9 | hostIPC: true #false
10 | containers:
11 | - name: nginx
12 | image: nginx
13 |
--------------------------------------------------------------------------------
/proc-mount/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-proc-mount
5 | labels:
6 | app: nginx-proc-mount
7 | spec:
8 | containers:
9 | - name: nginx
10 | image: nginx
11 | securityContext:
12 | procMount: Unmasked #Default
13 |
--------------------------------------------------------------------------------
/privileged-containers/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-privileged
5 | labels:
6 | app: nginx-privileged
7 | spec:
8 | containers:
9 | - name: nginx
10 | image: nginx
11 | securityContext:
12 | privileged: true #false
13 |
--------------------------------------------------------------------------------
/proc-mount/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPProcMount
3 | metadata:
4 | name: psp-proc-mount
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 | parameters:
11 | procMount: Default
12 |
--------------------------------------------------------------------------------
/allow-privilege-escalation/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPAllowPrivilegeEscalationContainer
3 | metadata:
4 | name: psp-allow-privilege-escalation-container
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 |
--------------------------------------------------------------------------------
/allow-privilege-escalation/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-privilege-escalation
5 | labels:
6 | app: nginx-privilege-escalation
7 | spec:
8 | containers:
9 | - name: nginx
10 | image: nginx
11 | securityContext:
12 | allowPrivilegeEscalation: true #false
13 |
--------------------------------------------------------------------------------
/read-only-root-filesystem/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-readonlyrootfilesystem
5 | labels:
6 | app: nginx-readonlyrootfilesystem
7 | spec:
8 | containers:
9 | - name: nginx
10 | image: nginx
11 | securityContext:
12 | readOnlyRootFilesystem: false #true
13 |
--------------------------------------------------------------------------------
/host-network-ports/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPHostNetworkingPorts
3 | metadata:
4 | name: psp-host-network-ports
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 | parameters:
11 | hostNetwork: true
12 | min: 80
13 | max: 9000
14 |
--------------------------------------------------------------------------------
/host-network-ports/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-host-networking-ports
5 | labels:
6 | app: nginx-host-networking-ports
7 | spec:
8 | hostNetwork: true #false
9 | containers:
10 | - name: nginx
11 | image: nginx
12 | ports:
13 | - containerPort: 9001
14 | hostPort: 9001
15 |
--------------------------------------------------------------------------------
/host-filesystem/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPHostFilesystem
3 | metadata:
4 | name: psp-host-filesystem
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 | parameters:
11 | allowedHostPaths:
12 | - readOnly: true
13 | pathPrefix: "/foo"
14 |
--------------------------------------------------------------------------------
/fsgroup/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPFSGroup
3 | metadata:
4 | name: psp-fsgroup
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 | parameters:
11 | rule: "MayRunAs" #"MustRunAs" #"MayRunAs", "RunAsAny"
12 | ranges:
13 | - min: 1
14 | max: 1000
15 |
--------------------------------------------------------------------------------
/flexvolume-drivers/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPFlexVolumes
3 | metadata:
4 | name: psp-flexvolume-drivers
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 | parameters:
11 | allowedFlexVolumes: #[]
12 | - driver: "example/lvm"
13 | - driver: "example/cifs"
14 |
--------------------------------------------------------------------------------
/host-namespaces/src.rego:
--------------------------------------------------------------------------------
1 | package k8spsphostnamespace
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | input_share_hostnamespace(input.review.object)
5 | msg := sprintf("Sharing the host namespace is not allowed: %v", [input.review.object.metadata.name])
6 | }
7 |
8 | input_share_hostnamespace(o) {
9 | o.spec.hostPID
10 | }
11 | input_share_hostnamespace(o) {
12 | o.spec.hostIPC
13 | }
14 |
--------------------------------------------------------------------------------
/host-filesystem/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-host-filesystem
5 | labels:
6 | app: nginx-host-filesystem
7 | spec:
8 | containers:
9 | - name: nginx
10 | image: nginx
11 | volumeMounts:
12 | - mountPath: /cache
13 | name: cache-volume
14 | readOnly: true
15 | volumes:
16 | - name: cache-volume
17 | hostPath:
18 | path: /tmp # directory location on host
19 |
--------------------------------------------------------------------------------
/flexvolume-drivers/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-flexvolume-driver
5 | labels:
6 | app: nginx-flexvolume-driver
7 | spec:
8 | containers:
9 | - name: nginx
10 | image: nginx
11 | volumeMounts:
12 | - mountPath: /test
13 | name: test-volume
14 | readOnly: true
15 | volumes:
16 | - name: test-volume
17 | flexVolume:
18 | driver: "example/testdriver" #"example/lvm"
19 |
--------------------------------------------------------------------------------
/fsgroup/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: fsgroup-demo
5 | spec:
6 | securityContext:
7 | fsGroup: 2000 # directory will have group ID 2000
8 | volumes:
9 | - name: fsgroup-demo-vol
10 | emptyDir: {}
11 | containers:
12 | - name: fsgroup-demo
13 | image: busybox
14 | command: [ "sh", "-c", "sleep 1h" ]
15 | volumeMounts:
16 | - name: fsgroup-demo-vol
17 | mountPath: /data/demo
18 |
--------------------------------------------------------------------------------
/privileged-containers/src.rego:
--------------------------------------------------------------------------------
1 | package k8spspprivileged
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | c := input_containers[_]
5 | c.securityContext.privileged
6 | msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
7 | }
8 |
9 | input_containers[c] {
10 | c := input.review.object.spec.containers[_]
11 | }
12 |
13 | input_containers[c] {
14 | c := input.review.object.spec.initContainers[_]
15 | }
16 |
--------------------------------------------------------------------------------
/volumes/constraint.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: constraints.gatekeeper.sh/v1alpha1
2 | kind: K8sPSPVolumeTypes
3 | metadata:
4 | name: psp-volume-types
5 | spec:
6 | match:
7 | kinds:
8 | - apiGroups: [""]
9 | kinds: ["Pod"]
10 | parameters:
11 | volumes:
12 | # - "*" # * may be used to allow all volume types
13 | - configMap
14 | - emptyDir
15 | - projected
16 | - secret
17 | - downwardAPI
18 | - persistentVolumeClaim
19 | #- hostPath #required for allowedHostPaths
20 | - flexVolume #required for allowedFlexVolumes
21 |
--------------------------------------------------------------------------------
/volumes/example.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: nginx-volume-types
5 | labels:
6 | app: nginx-volume-types
7 | spec:
8 | containers:
9 | - name: nginx
10 | image: nginx
11 | volumeMounts:
12 | - mountPath: /cache
13 | name: cache-volume
14 | - name: nginx2
15 | image: nginx
16 | volumeMounts:
17 | - mountPath: /cache2
18 | name: demo-vol
19 | volumes:
20 | - name: cache-volume
21 | hostPath:
22 | path: /tmp # directory location on host
23 | - name: demo-vol
24 | emptyDir: {}
25 |
--------------------------------------------------------------------------------
/volumes/src.rego:
--------------------------------------------------------------------------------
1 | package k8spspvolumetypes
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | volume_fields := {x | input.review.object.spec.volumes[_][x]; x != "name"}
5 | not input_volume_type_allowed(volume_fields)
6 | msg := sprintf("One of the volume types %v is not allowed, pod: %v. Allowed volume types: %v", [volume_fields, input.review.object.metadata.name, input.parameters.volumes])
7 | }
8 |
9 | # * may be used to allow all volume types
10 | input_volume_type_allowed(volume_fields) {
11 | input.parameters.volumes[_] == "*"
12 | }
13 |
14 | input_volume_type_allowed(volume_fields) {
15 | allowed_set := {x | x = input.parameters.volumes[_]}
16 | test := volume_fields - allowed_set
17 | count(test) == 0
18 | }
19 |
--------------------------------------------------------------------------------
/read-only-root-filesystem/src.rego:
--------------------------------------------------------------------------------
1 | package k8spspreadonlyrootfilesystem
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | c := input_containers[_]
5 | input_read_only_root_fs(c)
6 | msg := sprintf("only read-only root filesystem container is allowed: %v", [c.name])
7 | }
8 |
9 | input_read_only_root_fs(c) {
10 | not has_field(c, "securityContext")
11 | }
12 | input_read_only_root_fs(c) {
13 | has_field(c, "securityContext")
14 | not has_field(c.securityContext, "readOnlyRootFilesystem")
15 | }
16 |
17 | input_containers[c] {
18 | c := input.review.object.spec.containers[_]
19 | }
20 | input_containers[c] {
21 | c := input.review.object.spec.initContainers[_]
22 | }
23 |
24 | # has_field returns whether an object has a field
25 | has_field(object, field) = true {
26 | object[field]
27 | }
28 |
--------------------------------------------------------------------------------
/allow-privilege-escalation/src.rego:
--------------------------------------------------------------------------------
1 | package k8spspallowprivilegeescalationcontainer
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | c := input_containers[_]
5 | input_allow_priviledge_escalation_allowed(c)
6 | msg := sprintf("Privilege escalation container is not allowed: %v", [c.name])
7 | }
8 |
9 | input_allow_priviledge_escalation_allowed(c) {
10 | not has_field(c, "securityContext")
11 | }
12 | input_allow_priviledge_escalation_allowed(c) {
13 | has_field(c, "securityContext")
14 | has_field(c.securityContext, "allowPrivilegeEscalation")
15 | }
16 | input_containers[c] {
17 | c := input.review.object.spec.containers[_]
18 | }
19 | input_containers[c] {
20 | c := input.review.object.spec.initContainers[_]
21 | }
22 | # has_field returns whether an object has a field
23 | has_field(object, field) = true {
24 | object[field]
25 | }
26 |
--------------------------------------------------------------------------------
/host-namespaces/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spsphostnamespace
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPHostNamespace
10 | listKind: K8sPSPHostNamespaceList
11 | plural: k8spsphostnamespace
12 | singular: k8spsphostnamespace
13 | targets:
14 | - target: admission.k8s.gatekeeper.sh
15 | rego: |
16 | package k8spsphostnamespace
17 |
18 | violation[{"msg": msg, "details": {}}] {
19 | input_share_hostnamespace(input.review.object)
20 | msg := sprintf("Sharing the host namespace is not allowed: %v", [input.review.object.metadata.name])
21 | }
22 |
23 | input_share_hostnamespace(o) {
24 | o.spec.hostPID
25 | }
26 | input_share_hostnamespace(o) {
27 | o.spec.hostIPC
28 | }
29 |
--------------------------------------------------------------------------------
/proc-mount/src.rego:
--------------------------------------------------------------------------------
1 | package k8spspprocmount
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | c := input_containers[_]
5 | not input_proc_mount_type_allowed(c)
6 | msg := sprintf("ProcMount type is not allowed, container: %v. Allowed procMount types: %v", [c.name, input.parameters.procMount])
7 | }
8 |
9 | input_proc_mount_type_allowed(c) {
10 | input.parameters.procMount == c.securityContext.procMount
11 | }
12 |
13 | input_containers[c] {
14 | c := input.review.object.spec.containers[_]
15 | has_field(c, "securityContext")
16 | has_field(c.securityContext, "procMount")
17 | }
18 |
19 | input_containers[c] {
20 | c := input.review.object.spec.initContainers[_]
21 | has_field(c, "securityContext")
22 | has_field(c.securityContext, "procMount")
23 | }
24 |
25 | # has_field returns whether an object has a field
26 | has_field(object, field) = true {
27 | object[field]
28 | }
29 |
--------------------------------------------------------------------------------
/flexvolume-drivers/src.rego:
--------------------------------------------------------------------------------
1 | package k8spspflexvolumes
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | volume := input_flexvolumes[_]
5 | not input_flexvolumes_allowed(volume)
6 | msg := sprintf("FlexVolume %v is not allowed, pod: %v. Allowed drivers: %v", [volume, input.review.object.metadata.name, input.parameters.allowedFlexVolumes])
7 | }
8 |
9 | input_flexvolumes_allowed(volume) {
10 | # An empty list means there is no restriction on flexVolume drivers used
11 | input.parameters.allowedFlexVolumes == []
12 | }
13 |
14 | input_flexvolumes_allowed(volume) {
15 | input.parameters.allowedFlexVolumes[_].driver == volume.flexVolume.driver
16 | }
17 |
18 | input_flexvolumes[v] {
19 | v := input.review.object.spec.volumes[_]
20 | has_field(v, "flexVolume")
21 | }
22 |
23 | # has_field returns whether an object has a field
24 | has_field(object, field) = true {
25 | object[field]
26 | }
27 |
--------------------------------------------------------------------------------
/host-network-ports/src.rego:
--------------------------------------------------------------------------------
1 | package k8spsphostnetworkingports
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | input_share_hostnetwork(input.review.object)
5 | msg := sprintf("The specified hostNetwork and hostPort are not allowed, pod: %v. Allowed values: %v", [input.review.object.metadata.name, input.parameters])
6 | }
7 |
8 | input_share_hostnetwork(o) {
9 | not input.parameters.hostNetwork
10 | o.spec.hostNetwork
11 | }
12 |
13 | input_share_hostnetwork(o) {
14 | count(input_containers[_].ports) > 0
15 | hostPort := input_containers[_].ports[_].hostPort
16 | hostPort < input.parameters.min
17 | }
18 |
19 | input_share_hostnetwork(o) {
20 | count(input_containers[_].ports) > 0
21 | hostPort := input_containers[_].ports[_].hostPort
22 | hostPort > input.parameters.max
23 | }
24 |
25 | input_containers[c] {
26 | c := input.review.object.spec.containers[_]
27 | }
28 |
29 | input_containers[c] {
30 | c := input.review.object.spec.initContainers[_]
31 | }
32 |
--------------------------------------------------------------------------------
/privileged-containers/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spspprivilegedcontainer
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPPrivilegedContainer
10 | listKind: K8sPSPPrivilegedContainerList
11 | plural: k8spspprivilegedcontainer
12 | singular: k8spspprivilegedcontainer
13 | targets:
14 | - target: admission.k8s.gatekeeper.sh
15 | rego: |
16 | package k8spspprivileged
17 |
18 | violation[{"msg": msg, "details": {}}] {
19 | c := input_containers[_]
20 | c.securityContext.privileged
21 | msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
22 | }
23 |
24 | input_containers[c] {
25 | c := input.review.object.spec.containers[_]
26 | }
27 |
28 | input_containers[c] {
29 | c := input.review.object.spec.initContainers[_]
30 | }
31 |
--------------------------------------------------------------------------------
/proc-mount/README.md:
--------------------------------------------------------------------------------
1 | # ProcMount security context policy
2 |
3 | `procMount` denotes the type of proc mount to use for the containers. The default is `DefaultProcMount` which uses the container runtime defaults for readonly paths and masked paths.
4 |
5 | Types of proc mount are:
6 |
7 | - `DefaultProcMount` uses the container runtime default ProcType. Most container runtimes mask certain paths in /proc to avoid accidental security exposure of special devices or information.
8 |
9 | - `UnmaskedProcMount` bypasses the default masking behavior of the container runtime and ensures the newly created /proc the container stays in tact with no modifications.
10 |
11 | This requires the `ProcMountType` feature flag to be enabled. Set `--feature-gates=ProcMountType=true` in Kubernetes API Server to be able to use `Unmasked` procMount type (requires v1.12 and above). For more information, see
12 | https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/#options and https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/.
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Rita Zhang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/read-only-root-filesystem/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spspreadonlyrootfilesystem
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPReadOnlyRootFilesystem
10 | listKind: K8sPSPReadOnlyRootFilesystemContainerList
11 | plural: k8spspreadonlyfilesystem
12 | singular: k8spspreadonlyfilesystem
13 | targets:
14 | - target: admission.k8s.gatekeeper.sh
15 | rego: |
16 | package k8spspreadonlyrootfilesystem
17 |
18 | violation[{"msg": msg, "details": {}}] {
19 | c := input_containers[_]
20 | input_read_only_root_fs(c)
21 | msg := sprintf("only read-only root filesystem container is allowed: %v", [c.name])
22 | }
23 |
24 | input_read_only_root_fs(c) {
25 | not has_field(c, "securityContext")
26 | }
27 | input_read_only_root_fs(c) {
28 | has_field(c, "securityContext")
29 | not has_field(c.securityContext, "readOnlyRootFilesystem")
30 | }
31 |
32 | input_containers[c] {
33 | c := input.review.object.spec.containers[_]
34 | }
35 | input_containers[c] {
36 | c := input.review.object.spec.initContainers[_]
37 | }
38 |
39 | # has_field returns whether an object has a field
40 | has_field(object, field) = true {
41 | object[field]
42 | }
43 |
--------------------------------------------------------------------------------
/volumes/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spspvolumetypes
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPVolumeTypes
10 | listKind: K8sPSPVolumeTypesList
11 | plural: k8spspvolumetypes
12 | singular: k8spspvolumetypes
13 | validation:
14 | # Schema for the `parameters` field
15 | openAPIV3Schema:
16 | properties:
17 | volumes:
18 | type: array
19 | items:
20 | type: string
21 | targets:
22 | - target: admission.k8s.gatekeeper.sh
23 | rego: |
24 | package k8spspvolumetypes
25 |
26 | violation[{"msg": msg, "details": {}}] {
27 | volume_fields := {x | input.review.object.spec.volumes[_][x]; x != "name"}
28 | not input_volume_type_allowed(volume_fields)
29 | msg := sprintf("One of the volume types %v is not allowed, pod: %v. Allowed volume types: %v", [volume_fields, input.review.object.metadata.name, input.parameters.volumes])
30 | }
31 |
32 | # * may be used to allow all volume types
33 | input_volume_type_allowed(volume_fields) {
34 | input.parameters.volumes[_] == "*"
35 | }
36 |
37 | input_volume_type_allowed(volume_fields) {
38 | allowed_set := {x | x = input.parameters.volumes[_]}
39 | test := volume_fields - allowed_set
40 | count(test) == 0
41 | }
42 |
--------------------------------------------------------------------------------
/allow-privilege-escalation/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spspallowprivilegeescalationcontainer
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPAllowPrivilegeEscalationContainer
10 | listKind: K8sPSPAllowPrivilegeEscalationContainerList
11 | plural: k8spspallowprivilegeescalationcontainer
12 | singular: k8spspallowprivilegeescalationcontainer
13 | targets:
14 | - target: admission.k8s.gatekeeper.sh
15 | rego: |
16 | package k8spspallowprivilegeescalationcontainer
17 |
18 | violation[{"msg": msg, "details": {}}] {
19 | c := input_containers[_]
20 | input_allow_priviledge_escalation_allowed(c)
21 | msg := sprintf("Privilege escalation container is not allowed: %v", [c.name])
22 | }
23 |
24 | input_allow_priviledge_escalation_allowed(c) {
25 | not has_field(c, "securityContext")
26 | }
27 | input_allow_priviledge_escalation_allowed(c) {
28 | has_field(c, "securityContext")
29 | has_field(c.securityContext, "allowPrivilegeEscalation")
30 | }
31 | input_containers[c] {
32 | c := input.review.object.spec.containers[_]
33 | }
34 | input_containers[c] {
35 | c := input.review.object.spec.initContainers[_]
36 | }
37 | # has_field returns whether an object has a field
38 | has_field(object, field) = true {
39 | object[field]
40 | }
41 |
--------------------------------------------------------------------------------
/proc-mount/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spspprocmount
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPProcMount
10 | listKind: K8sPSPProcMountList
11 | plural: k8spspprocmount
12 | singular: k8spspprocmount
13 | validation:
14 | # Schema for the `parameters` field
15 | openAPIV3Schema:
16 | properties:
17 | procMount:
18 | type: string
19 | targets:
20 | - target: admission.k8s.gatekeeper.sh
21 | rego: |
22 | package k8spspprocmount
23 |
24 | violation[{"msg": msg, "details": {}}] {
25 | c := input_containers[_]
26 | not input_proc_mount_type_allowed(c)
27 | msg := sprintf("ProcMount type is not allowed, container: %v. Allowed procMount types: %v", [c.name, input.parameters.procMount])
28 | }
29 |
30 | input_proc_mount_type_allowed(c) {
31 | input.parameters.procMount == c.securityContext.procMount
32 | }
33 |
34 | input_containers[c] {
35 | c := input.review.object.spec.containers[_]
36 | has_field(c, "securityContext")
37 | has_field(c.securityContext, "procMount")
38 | }
39 |
40 | input_containers[c] {
41 | c := input.review.object.spec.initContainers[_]
42 | has_field(c, "securityContext")
43 | has_field(c.securityContext, "procMount")
44 | }
45 |
46 | # has_field returns whether an object has a field
47 | has_field(object, field) = true {
48 | object[field]
49 | }
50 |
--------------------------------------------------------------------------------
/flexvolume-drivers/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spspflexvolumes
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPFlexVolumes
10 | listKind: K8sPSPFlexVolumesList
11 | plural: k8spspflexvolumes
12 | singular: k8spspflexvolumes
13 | validation:
14 | # Schema for the `parameters` field
15 | openAPIV3Schema:
16 | properties:
17 | allowedFlexVolumes:
18 | type: array
19 | items:
20 | type: object
21 | properties:
22 | driver:
23 | type: string
24 | targets:
25 | - target: admission.k8s.gatekeeper.sh
26 | rego: |
27 | package k8spspflexvolumes
28 |
29 | violation[{"msg": msg, "details": {}}] {
30 | volume := input_flexvolumes[_]
31 | not input_flexvolumes_allowed(volume)
32 | msg := sprintf("FlexVolume %v is not allowed, pod: %v. Allowed drivers: %v", [volume, input.review.object.metadata.name, input.parameters.allowedFlexVolumes])
33 | }
34 |
35 | input_flexvolumes_allowed(volume) {
36 | # An empty list means there is no restriction on flexVolume drivers used
37 | input.parameters.allowedFlexVolumes == []
38 | }
39 |
40 | input_flexvolumes_allowed(volume) {
41 | input.parameters.allowedFlexVolumes[_].driver == volume.flexVolume.driver
42 | }
43 |
44 | input_flexvolumes[v] {
45 | v := input.review.object.spec.volumes[_]
46 | has_field(v, "flexVolume")
47 | }
48 |
49 | # has_field returns whether an object has a field
50 | has_field(object, field) = true {
51 | object[field]
52 | }
53 |
--------------------------------------------------------------------------------
/host-namespaces/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spsphostnamespace
2 |
3 | test_input_no_hostnamespace_allowed {
4 | input := { "review": input_review}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 | test_input_hostPID_not_allowed {
9 | input := { "review": input_review_hostPID}
10 | results := violation with input as input
11 | count(results) > 0
12 | }
13 | test_input_hostIPC_not_allowed {
14 | input := { "review": input_review_hostIPC}
15 | results := violation with input as input
16 | count(results) > 0
17 | }
18 | test_input_hostnamespace_both_not_allowed {
19 | input := { "review": input_review_hostnamespace_both}
20 | results := violation with input as input
21 | count(results) > 0
22 | }
23 |
24 | input_review = {
25 | "object": {
26 | "metadata": {
27 | "name": "nginx"
28 | },
29 | "spec": {
30 | "containers": input_containers
31 | }
32 | }
33 | }
34 | input_review_hostPID = {
35 | "object": {
36 | "metadata": {
37 | "name": "nginx"
38 | },
39 | "spec": {
40 | "hostPID": true,
41 | "containers": input_containers
42 | }
43 | }
44 | }
45 |
46 | input_review_hostIPC = {
47 | "object": {
48 | "metadata": {
49 | "name": "nginx"
50 | },
51 | "spec": {
52 | "hostIPC": true,
53 | "containers": input_containers
54 | }
55 | }
56 | }
57 | input_review_hostnamespace_both = {
58 | "object": {
59 | "metadata": {
60 | "name": "nginx"
61 | },
62 | "spec": {
63 |
64 | "hostPID": true,
65 | "hostIPC": true,
66 | "containers": input_containers
67 | }
68 | }
69 | }
70 | input_containers = [
71 | {
72 | "name": "nginx",
73 | "image": "nginx"
74 | }]
75 |
--------------------------------------------------------------------------------
/host-network-ports/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spsphostnetworkingports
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPHostNetworkingPorts
10 | listKind: K8sPSPHostNetworkingPortsList
11 | plural: k8spsphostnetworkingports
12 | singular: k8spsphostnetworkingports
13 | validation:
14 | # Schema for the `parameters` field
15 | openAPIV3Schema:
16 | properties:
17 | hostNetwork:
18 | type: boolean
19 | min:
20 | type: integer
21 | max:
22 | type: integer
23 | targets:
24 | - target: admission.k8s.gatekeeper.sh
25 | rego: |
26 | package k8spsphostnetworkingports
27 |
28 | violation[{"msg": msg, "details": {}}] {
29 | input_share_hostnetwork(input.review.object)
30 | msg := sprintf("The specified hostNetwork and hostPort are not allowed, pod: %v. Allowed values: %v", [input.review.object.metadata.name, input.parameters])
31 | }
32 |
33 | input_share_hostnetwork(o) {
34 | not input.parameters.hostNetwork
35 | o.spec.hostNetwork
36 | }
37 |
38 | input_share_hostnetwork(o) {
39 | count(input_containers[_].ports) > 0
40 | hostPort := input_containers[_].ports[_].hostPort
41 | hostPort < input.parameters.min
42 | }
43 |
44 | input_share_hostnetwork(o) {
45 | count(input_containers[_].ports) > 0
46 | hostPort := input_containers[_].ports[_].hostPort
47 | hostPort > input.parameters.max
48 | }
49 |
50 | input_containers[c] {
51 | c := input.review.object.spec.containers[_]
52 | }
53 |
54 | input_containers[c] {
55 | c := input.review.object.spec.initContainers[_]
56 | }
57 |
--------------------------------------------------------------------------------
/fsgroup/src.rego:
--------------------------------------------------------------------------------
1 | package k8spspfsgroup
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | spec := input.review.object.spec
5 | not input_fsGroup_allowed(spec)
6 | msg := sprintf("The provided pod spec fsGroup is not allowed, pod: %v. Allowed fsGroup: %v", [input.review.object.metadata.name, input.parameters])
7 | }
8 |
9 | input_fsGroup_allowed(spec) {
10 | # RunAsAny - No range is required. Allows any fsGroup ID to be specified.
11 | input.parameters.rule == "RunAsAny"
12 | }
13 | input_fsGroup_allowed(spec) {
14 | # MustRunAs - Validates pod spec fsgroup against all ranges
15 | input.parameters.rule == "MustRunAs"
16 | fg := spec.securityContext.fsGroup
17 | count(input.parameters.ranges) > 0
18 | range := input.parameters.ranges[_]
19 | value_within_range(range, fg)
20 | }
21 | input_fsGroup_allowed(spec) {
22 | # MayRunAs - Validates pod spec fsgroup against all ranges or allow pod spec fsgroup to be left unset
23 | input.parameters.rule == "MayRunAs"
24 | not has_field(spec, "securityContext")
25 | }
26 | input_fsGroup_allowed(spec) {
27 | # MayRunAs - Validates pod spec fsgroup against all ranges or allow pod spec fsgroup to be left unset
28 | input.parameters.rule == "MayRunAs"
29 | has_field(spec, "securityContext")
30 | not has_field(spec.securityContext, "fsGroup")
31 | }
32 | input_fsGroup_allowed(spec) {
33 | # MayRunAs - Validates pod spec fsgroup against all ranges or allow pod spec fsgroup to be left unset
34 | input.parameters.rule == "MayRunAs"
35 | fg := spec.securityContext.fsGroup
36 | count(input.parameters.ranges) > 0
37 | range := input.parameters.ranges[_]
38 | value_within_range(range, fg)
39 | }
40 | value_within_range(range, value) {
41 | range.min <= value
42 | range.max >= value
43 | }
44 | # has_field returns whether an object has a field
45 | has_field(object, field) = true {
46 | object[field]
47 | }
48 |
--------------------------------------------------------------------------------
/host-filesystem/src.rego:
--------------------------------------------------------------------------------
1 | package k8spsphostfilesystem
2 |
3 | violation[{"msg": msg, "details": {}}] {
4 | volume := input_hostpath_volumes[_]
5 | not input_hostpath_allowed(volume)
6 | msg := sprintf("HostPath volume %v is not allowed, pod: %v. Allowed path: %v", [volume, input.review.object.metadata.name, input.parameters.allowedHostPaths])
7 | }
8 |
9 | input_hostpath_allowed(volume) {
10 | # An empty list means there is no restriction on host paths used
11 | input.parameters.allowedHostPaths == []
12 | }
13 |
14 | input_hostpath_allowed(volume) {
15 | allowedHostPath := input.parameters.allowedHostPaths[_]
16 | path_matches(allowedHostPath.pathPrefix, volume.hostPath.path)
17 | not allowedHostPath.readOnly
18 | }
19 |
20 | input_hostpath_allowed(volume) {
21 | allowedHostPath := input.parameters.allowedHostPaths[_]
22 | path_matches(allowedHostPath.pathPrefix, volume.hostPath.path)
23 | allowedHostPath.readOnly
24 | not writeable_input_volume_mounts(volume.name)
25 | }
26 |
27 | writeable_input_volume_mounts(volume_name) {
28 | container := input_containers[_]
29 | mount := container.volumeMounts[_]
30 | mount.name == volume_name
31 | not mount.readOnly
32 | }
33 |
34 | # This allows "/foo", "/foo/", "/foo/bar" etc., but
35 | # disallows "/fool", "/etc/foo" etc.
36 | path_matches(prefix, path) {
37 | a := split(trim(prefix, "/"), "/")
38 | b := split(trim(path, "/"), "/")
39 | prefix_matches(a, b)
40 | }
41 | prefix_matches(a, b) {
42 | count(a) <= count(b)
43 | not any_not_equal_upto(a, b, count(a))
44 | }
45 |
46 | any_not_equal_upto(a, b, n) {
47 | a[i] != b[i]
48 | i < n
49 | }
50 |
51 | input_hostpath_volumes[v] {
52 | v := input.review.object.spec.volumes[_]
53 | has_field(v, "hostPath")
54 | }
55 |
56 | # has_field returns whether an object has a field
57 | has_field(object, field) = true {
58 | object[field]
59 | }
60 | input_containers[c] {
61 | c := input.review.object.spec.containers[_]
62 | }
63 |
64 | input_containers[c] {
65 | c := input.review.object.spec.initContainers[_]
66 | }
67 |
--------------------------------------------------------------------------------
/read-only-root-filesystem/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spspreadonlyrootfilesystem
2 |
3 | test_input_container_not_readonlyrootfilesystem_allowed {
4 | input := { "review": input_review}
5 | results := violation with input as input
6 | count(results) == 1
7 | }
8 | test_input_container_readonlyrootfilesystem_not_allowed {
9 | input := { "review": input_review_ro}
10 | results := violation with input as input
11 | count(results) == 0
12 | }
13 | test_input_container_many_mixed_readonlyrootfilesystem_not_allowed {
14 | input := { "review": input_review_many_mixed}
15 | results := violation with input as input
16 | count(results) == 2
17 | }
18 | test_input_container_many_mixed_readonlyrootfilesystem_not_allowed_two {
19 | input := { "review": input_review_many_mixed_two}
20 | results := violation with input as input
21 | count(results) == 1
22 | }
23 |
24 | input_review = {
25 | "object": {
26 | "metadata": {
27 | "name": "nginx"
28 | },
29 | "spec": {
30 | "containers": input_containers_one
31 | }
32 | }
33 | }
34 |
35 | input_review_ro = {
36 | "object": {
37 | "metadata": {
38 | "name": "nginx"
39 | },
40 | "spec": {
41 | "containers": input_containers_one_ro
42 | }
43 | }
44 | }
45 |
46 | input_review_many_mixed = {
47 | "object": {
48 | "metadata": {
49 | "name": "nginx"
50 | },
51 | "spec": {
52 | "containers": input_containers_many,
53 | "initContainers": input_containers_one
54 | }
55 | }
56 | }
57 |
58 | input_review_many_mixed_two = {
59 | "object": {
60 | "metadata": {
61 | "name": "nginx"
62 | },
63 | "spec": {
64 | "containers": input_containers_many_mixed,
65 | "initContainers": input_containers_one_ro
66 | }
67 | }
68 | }
69 |
70 | input_containers_one = [
71 | {
72 | "name": "nginx",
73 | "image": "nginx",
74 | "securityContext": {
75 | "readOnlyRootFilesystem": false
76 | }
77 | }]
78 |
79 | input_containers_one_ro = [
80 | {
81 | "name": "nginx",
82 | "image": "nginx",
83 | "securityContext": {
84 | "readOnlyRootFilesystem": true
85 | }
86 | }]
87 |
88 | input_containers_many = [
89 | {
90 | "name": "nginx",
91 | "image": "nginx",
92 | "securityContext": {
93 | "readOnlyRootFilesystem": true
94 | }
95 | },
96 | {
97 | "name": "nginx1",
98 | "image": "nginx"
99 | }]
100 |
101 | input_containers_many_mixed = [
102 | {
103 | "name": "nginx",
104 | "image": "nginx",
105 | "securityContext": {
106 | "readOnlyRootFilesystem": false
107 | }
108 | },
109 | {
110 | "name": "nginx1",
111 | "image": "nginx",
112 | "securityContext": {
113 | "readOnlyRootFilesystem": true
114 | }
115 | }]
116 |
--------------------------------------------------------------------------------
/fsgroup/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spspfsgroup
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPFSGroup
10 | listKind: K8sPSPFSGroupList
11 | plural: k8spspfsgroup
12 | singular: k8spspfsgroup
13 | validation:
14 | # Schema for the `parameters` field
15 | openAPIV3Schema:
16 | properties:
17 | rule:
18 | type: string
19 | ranges:
20 | type: array
21 | items:
22 | type: object
23 | properties:
24 | min:
25 | type: integer
26 | max:
27 | type: integer
28 | targets:
29 | - target: admission.k8s.gatekeeper.sh
30 | rego: |
31 | package k8spspfsgroup
32 |
33 | violation[{"msg": msg, "details": {}}] {
34 | spec := input.review.object.spec
35 | not input_fsGroup_allowed(spec)
36 | msg := sprintf("The provided pod spec fsGroup is not allowed, pod: %v. Allowed fsGroup: %v", [input.review.object.metadata.name, input.parameters])
37 | }
38 |
39 | input_fsGroup_allowed(spec) {
40 | # RunAsAny - No range is required. Allows any fsGroup ID to be specified.
41 | input.parameters.rule == "RunAsAny"
42 | }
43 | input_fsGroup_allowed(spec) {
44 | # MustRunAs - Validates pod spec fsgroup against all ranges
45 | input.parameters.rule == "MustRunAs"
46 | fg := spec.securityContext.fsGroup
47 | count(input.parameters.ranges) > 0
48 | range := input.parameters.ranges[_]
49 | value_within_range(range, fg)
50 | }
51 | input_fsGroup_allowed(spec) {
52 | # MayRunAs - Validates pod spec fsgroup against all ranges or allow pod spec fsgroup to be left unset
53 | input.parameters.rule == "MayRunAs"
54 | not has_field(spec, "securityContext")
55 | }
56 | input_fsGroup_allowed(spec) {
57 | # MayRunAs - Validates pod spec fsgroup against all ranges or allow pod spec fsgroup to be left unset
58 | input.parameters.rule == "MayRunAs"
59 | has_field(spec, "securityContext")
60 | not has_field(spec.securityContext, "fsGroup")
61 | }
62 | input_fsGroup_allowed(spec) {
63 | # MayRunAs - Validates pod spec fsgroup against all ranges or allow pod spec fsgroup to be left unset
64 | input.parameters.rule == "MayRunAs"
65 | fg := spec.securityContext.fsGroup
66 | count(input.parameters.ranges) > 0
67 | range := input.parameters.ranges[_]
68 | value_within_range(range, fg)
69 | }
70 | value_within_range(range, value) {
71 | range.min <= value
72 | range.max >= value
73 | }
74 | # has_field returns whether an object has a field
75 | has_field(object, field) = true {
76 | object[field]
77 | }
78 |
--------------------------------------------------------------------------------
/volumes/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spspvolumetypes
2 |
3 | test_input_volume_type_allowed_all {
4 | input := { "review": input_review, "parameters": input_parameters_wildcard}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 |
9 | test_input_volume_type_allowed_in_list {
10 | input := { "review": input_review, "parameters": input_parameters_in_list}
11 | results := violation with input as input
12 | count(results) == 0
13 | }
14 |
15 | test_input_volume_type_allowed_not_in_list {
16 | input := { "review": input_review, "parameters": input_parameters_not_in_list}
17 | results := violation with input as input
18 | count(results) > 0
19 | }
20 |
21 | test_input_volume_type_allowed_in_list_many_volumes {
22 | input := { "review": input_review_many, "parameters": input_parameters_in_list}
23 | results := violation with input as input
24 | count(results) == 0
25 | }
26 |
27 | test_input_volume_type_allowed_not_all_in_list_many_volumes {
28 | input := { "review": input_review_many, "parameters": input_parameters_not_in_list}
29 | results := violation with input as input
30 | count(results) > 0
31 | }
32 |
33 | input_review = {
34 | "object": {
35 | "metadata": {
36 | "name": "nginx"
37 | },
38 | "spec": {
39 | "containers": input_containers,
40 | "volumes": input_volumes
41 | }
42 | }
43 | }
44 |
45 | input_review_many = {
46 | "object": {
47 | "metadata": {
48 | "name": "nginx"
49 | },
50 | "spec": {
51 | "containers": input_containers_many,
52 | "volumes": input_volumes_many
53 | }
54 | }
55 | }
56 |
57 | input_containers = [
58 | {
59 | "name": "nginx",
60 | "image": "nginx",
61 | "volumeMounts":[
62 | {
63 | "mountPath": "/cache",
64 | "name": "cache-volume"
65 | }]
66 | }]
67 |
68 | input_containers_many = [
69 | {
70 | "name": "nginx",
71 | "image": "nginx",
72 | "volumeMounts":[
73 | {
74 | "mountPath": "/cache",
75 | "name": "cache-volume"
76 | }]
77 | },
78 | {
79 | "name": "nginx2",
80 | "image": "nginx",
81 | "volumeMounts":[
82 | {
83 | "mountPath": "/cache",
84 | "name": "cache-volume2"
85 | }]
86 | }]
87 |
88 | input_volumes = [
89 | {
90 | "name": "cache-volume",
91 | "hostPath": {
92 | "path": "/tmp"
93 | }
94 | }]
95 |
96 | input_volumes_many = [
97 | {
98 | "name": "cache-volume",
99 | "hostPath": {
100 | "path": "/tmp"
101 | }
102 | },
103 | {
104 | "name": "cache-volume2",
105 | "emptyDir": {}
106 | }]
107 |
108 | input_parameters_wildcard = {
109 | "volumes": [
110 | "*"
111 | ]
112 | }
113 |
114 | input_parameters_in_list = {
115 | "volumes": [
116 | "hostPath",
117 | "emptyDir"
118 | ]
119 | }
120 |
121 | input_parameters_not_in_list = {
122 | "volumes": [
123 | "configMap",
124 | "emptyDir"
125 | ]
126 | }
127 |
--------------------------------------------------------------------------------
/host-filesystem/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: templates.gatekeeper.sh/v1alpha1
2 | kind: ConstraintTemplate
3 | metadata:
4 | name: k8spsphostfilesystem
5 | spec:
6 | crd:
7 | spec:
8 | names:
9 | kind: K8sPSPHostFilesystem
10 | listKind: K8sPSPHostFilesystemList
11 | plural: k8spsphostfilesystem
12 | singular: k8spsphostfilesystem
13 | validation:
14 | # Schema for the `parameters` field
15 | openAPIV3Schema:
16 | properties:
17 | allowedHostPaths:
18 | type: array
19 | items:
20 | type: object
21 | properties:
22 | readOnly:
23 | type: boolean
24 | pathPrefix:
25 | type: string
26 | targets:
27 | - target: admission.k8s.gatekeeper.sh
28 | rego: |
29 | package k8spsphostfilesystem
30 |
31 | violation[{"msg": msg, "details": {}}] {
32 | volume := input_hostpath_volumes[_]
33 | not input_hostpath_allowed(volume)
34 | msg := sprintf("HostPath volume %v is not allowed, pod: %v. Allowed path: %v", [volume, input.review.object.metadata.name, input.parameters.allowedHostPaths])
35 | }
36 |
37 | input_hostpath_allowed(volume) {
38 | # An empty list means there is no restriction on host paths used
39 | input.parameters.allowedHostPaths == []
40 | }
41 |
42 | input_hostpath_allowed(volume) {
43 | allowedHostPath := input.parameters.allowedHostPaths[_]
44 | path_matches(allowedHostPath.pathPrefix, volume.hostPath.path)
45 | not allowedHostPath.readOnly
46 | }
47 |
48 | input_hostpath_allowed(volume) {
49 | allowedHostPath := input.parameters.allowedHostPaths[_]
50 | path_matches(allowedHostPath.pathPrefix, volume.hostPath.path)
51 | allowedHostPath.readOnly
52 | not writeable_input_volume_mounts(volume.name)
53 | }
54 |
55 | writeable_input_volume_mounts(volume_name) {
56 | container := input_containers[_]
57 | mount := container.volumeMounts[_]
58 | mount.name == volume_name
59 | not mount.readOnly
60 | }
61 |
62 | # This allows "/foo", "/foo/", "/foo/bar" etc., but
63 | # disallows "/fool", "/etc/foo" etc.
64 | path_matches(prefix, path) {
65 | a := split(trim(prefix, "/"), "/")
66 | b := split(trim(path, "/"), "/")
67 | prefix_matches(a, b)
68 | }
69 | prefix_matches(a, b) {
70 | count(a) <= count(b)
71 | not any_not_equal_upto(a, b, count(a))
72 | }
73 |
74 | any_not_equal_upto(a, b, n) {
75 | a[i] != b[i]
76 | i < n
77 | }
78 |
79 | input_hostpath_volumes[v] {
80 | v := input.review.object.spec.volumes[_]
81 | has_field(v, "hostPath")
82 | }
83 |
84 | # has_field returns whether an object has a field
85 | has_field(object, field) = true {
86 | object[field]
87 | }
88 | input_containers[c] {
89 | c := input.review.object.spec.containers[_]
90 | }
91 |
92 | input_containers[c] {
93 | c := input.review.object.spec.initContainers[_]
94 | }
95 |
--------------------------------------------------------------------------------
/privileged-containers/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spspprivileged
2 |
3 | test_input_container_not_privileged_allowed {
4 | input := { "review": input_review}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 | test_input_container_privileged_not_allowed {
9 | input := { "review": input_review_priv}
10 | results := violation with input as input
11 | count(results) > 0
12 | }
13 | test_input_container_many_not_privileged_allowed {
14 | input := { "review": input_review_many}
15 | results := violation with input as input
16 | count(results) == 0
17 | }
18 | test_input_container_many_mixed_privileged_not_allowed {
19 | input := { "review": input_review_many_mixed}
20 | results := violation with input as input
21 | count(results) > 0
22 | }
23 | test_input_container_many_mixed_privileged_not_allowed_two {
24 | input := { "review": input_review_many_mixed_two}
25 | results := violation with input as input
26 | count(results) == 2
27 | }
28 |
29 | input_review = {
30 | "object": {
31 | "metadata": {
32 | "name": "nginx"
33 | },
34 | "spec": {
35 | "containers": input_containers_one
36 | }
37 | }
38 | }
39 |
40 | input_review_priv = {
41 | "object": {
42 | "metadata": {
43 | "name": "nginx"
44 | },
45 | "spec": {
46 | "containers": input_containers_one_priv
47 | }
48 | }
49 | }
50 |
51 | input_review_many = {
52 | "object": {
53 | "metadata": {
54 | "name": "nginx"
55 | },
56 | "spec": {
57 | "containers": input_containers_many,
58 | "initContainers": input_containers_one
59 | }
60 | }
61 | }
62 |
63 | input_review_many_mixed = {
64 | "object": {
65 | "metadata": {
66 | "name": "nginx"
67 | },
68 | "spec": {
69 | "containers": input_containers_many,
70 | "initContainers": input_containers_one_priv
71 | }
72 | }
73 | }
74 |
75 | input_review_many_mixed_two = {
76 | "object": {
77 | "metadata": {
78 | "name": "nginx"
79 | },
80 | "spec": {
81 | "containers": input_containers_many_mixed,
82 | "initContainers": input_containers_one_priv
83 | }
84 | }
85 | }
86 |
87 | input_containers_one = [
88 | {
89 | "name": "nginx",
90 | "image": "nginx",
91 | "securityContext": {
92 | "privileged": false
93 | }
94 | }]
95 |
96 | input_containers_one_priv = [
97 | {
98 | "name": "nginx",
99 | "image": "nginx",
100 | "securityContext": {
101 | "privileged": true
102 | }
103 | }]
104 |
105 | input_containers_many = [
106 | {
107 | "name": "nginx",
108 | "image": "nginx",
109 | "securityContext": {
110 | "privileged": false
111 | }
112 | },
113 | {
114 | "name": "nginx1",
115 | "image": "nginx"
116 | }]
117 |
118 | input_containers_many_mixed = [
119 | {
120 | "name": "nginx",
121 | "image": "nginx",
122 | "securityContext": {
123 | "privileged": false
124 | }
125 | },
126 | {
127 | "name": "nginx1",
128 | "image": "nginx",
129 | "securityContext": {
130 | "privileged": true
131 | }
132 | }]
133 |
--------------------------------------------------------------------------------
/host-filesystem/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spsphostfilesystem
2 |
3 | test_input_hostpath_allowed_all {
4 | input := { "review": input_review, "parameters": input_parameters_wildcard}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 | test_input_hostpath_allowed_readonly {
9 | input := { "review": input_review_many, "parameters": input_parameters_in_list}
10 | results := violation with input as input
11 | count(results) == 0
12 | }
13 | test_input_hostpath_allowed_not_writable {
14 | input := { "review": input_review, "parameters": input_parameters_in_list}
15 | results := violation with input as input
16 | count(results) > 0
17 | }
18 | test_input_hostpath_allowed_is_writable {
19 | input := { "review": input_review, "parameters": input_parameters_in_list_writable}
20 | results := violation with input as input
21 | count(results) == 0
22 | }
23 | test_input_hostpath_not_allowed {
24 | input := { "review": input_review, "parameters": input_parameters_not_in_list_writable}
25 | results := violation with input as input
26 | count(results) > 0
27 | }
28 |
29 | input_review = {
30 | "object": {
31 | "metadata": {
32 | "name": "nginx"
33 | },
34 | "spec": {
35 | "containers": input_containers_one,
36 | "volumes": input_volumes
37 | }
38 | }
39 | }
40 |
41 | input_review_many = {
42 | "object": {
43 | "metadata": {
44 | "name": "nginx"
45 | },
46 | "spec": {
47 | "containers": input_containers_many,
48 | "volumes": input_volumes_many
49 | }
50 | }
51 | }
52 |
53 | input_containers_one = [
54 | {
55 | "name": "nginx",
56 | "image": "nginx",
57 | "volumeMounts":[
58 | {
59 | "mountPath": "/cache",
60 | "name": "cache-volume"
61 | }]
62 | }]
63 |
64 | input_containers_many = [
65 | {
66 | "name": "nginx",
67 | "image": "nginx",
68 | "volumeMounts":[
69 | {
70 | "mountPath": "/cache",
71 | "name": "cache-volume",
72 | "readOnly": true
73 | }]
74 | },
75 | {
76 | "name": "nginx2",
77 | "image": "nginx",
78 | "volumeMounts":[
79 | {
80 | "mountPath": "/cache",
81 | "name": "cache-volume2",
82 | "readOnly": true
83 | }]
84 | }]
85 |
86 | input_volumes = [
87 | {
88 | "name": "cache-volume",
89 | "hostPath": {
90 | "path": "/tmp"
91 | }
92 | }]
93 |
94 | input_volumes_many = [
95 | {
96 | "name": "cache-volume",
97 | "hostPath": {
98 | "path": "/tmp"
99 | }
100 | },
101 | {
102 | "name": "cache-volume2",
103 | "hostPath": {
104 | "path": "/tmp/test"
105 | }
106 | }]
107 |
108 | input_parameters_wildcard = {
109 | "allowedHostPaths": []
110 | }
111 |
112 | input_parameters_in_list = {
113 | "allowedHostPaths": [
114 | {
115 | "readOnly": true,
116 | "pathPrefix": "/tmp"
117 | }]
118 | }
119 |
120 | input_parameters_in_list_writable = {
121 | "allowedHostPaths": [
122 | {
123 | "pathPrefix": "/tmp"
124 | },
125 | {
126 | "pathPrefix": "/foo"
127 | }]
128 | }
129 |
130 | input_parameters_not_in_list_writable = {
131 | "allowedHostPaths": [
132 | {
133 | "pathPrefix": "/foo"
134 | }]
135 | }
136 |
--------------------------------------------------------------------------------
/allow-privilege-escalation/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spspallowprivilegeescalationcontainer
2 |
3 | test_input_container_not_privilege_escalation_allowed {
4 | input := { "review": input_review}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 | test_input_container_privilege_escalation_not_allowed {
9 | input := { "review": input_review_priv}
10 | results := violation with input as input
11 | count(results) == 1
12 | }
13 | test_input_container_many_not_privilege_escalation_allowed {
14 | input := { "review": input_review_many}
15 | results := violation with input as input
16 | count(results) == 1
17 | }
18 | test_input_container_many_mixed_privilege_escalation_not_allowed {
19 | input := { "review": input_review_many_mixed}
20 | results := violation with input as input
21 | count(results) > 0
22 | }
23 | test_input_container_many_mixed_privilege_escalation_not_allowed_two {
24 | input := { "review": input_review_many_mixed_two}
25 | results := violation with input as input
26 | count(results) == 2
27 | }
28 |
29 | input_review = {
30 | "object": {
31 | "metadata": {
32 | "name": "nginx"
33 | },
34 | "spec": {
35 | "containers": input_containers_one
36 | }
37 | }
38 | }
39 |
40 | input_review_priv = {
41 | "object": {
42 | "metadata": {
43 | "name": "nginx"
44 | },
45 | "spec": {
46 | "containers": input_containers_one_priv,
47 | }
48 | }
49 | }
50 |
51 | input_review_many = {
52 | "object": {
53 | "metadata": {
54 | "name": "nginx"
55 | },
56 | "spec": {
57 | "containers": input_containers_many,
58 | "initContainers": input_containers_one
59 | }
60 | }
61 | }
62 |
63 | input_review_many_mixed = {
64 | "object": {
65 | "metadata": {
66 | "name": "nginx"
67 | },
68 | "spec": {
69 | "containers": input_containers_many,
70 | "initContainers": input_containers_one_priv
71 | }
72 | }
73 | }
74 |
75 | input_review_many_mixed_two = {
76 | "object": {
77 | "metadata": {
78 | "name": "nginx"
79 | },
80 | "spec": {
81 | "containers": input_containers_many_mixed,
82 | "initContainers": input_containers_one_priv
83 | }
84 | }
85 | }
86 |
87 | input_containers_one = [
88 | {
89 | "name": "nginx",
90 | "image": "nginx",
91 | "securityContext": {
92 | "allowPrivilegeEscalation": false
93 | }
94 | }]
95 |
96 | input_containers_one_priv = [
97 | {
98 | "name": "nginx",
99 | "image": "nginx",
100 | "securityContext": {
101 | "allowPrivilegeEscalation": true
102 | }
103 | }]
104 |
105 | input_containers_many = [
106 | {
107 | "name": "nginx",
108 | "image": "nginx",
109 | "securityContext": {
110 | "allowPrivilegeEscalation": false
111 | }
112 | },
113 | {
114 | "name": "nginx1",
115 | "image": "nginx"
116 | }]
117 |
118 | input_containers_many_mixed = [
119 | {
120 | "name": "nginx",
121 | "image": "nginx",
122 | "securityContext": {
123 | "allowPrivilegeEscalation": false
124 | }
125 | },
126 | {
127 | "name": "nginx1",
128 | "image": "nginx",
129 | "securityContext": {
130 | "allowPrivilegeEscalation": true
131 | }
132 | }]
133 |
--------------------------------------------------------------------------------
/fsgroup/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spspfsgroup
2 |
3 | test_input_fsgroup_allowed_all {
4 | input := { "review": input_review_with_fsgroup, "parameters": input_parameters_runasany}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 | test_input_no_fsgroup_allowed_all {
9 | input := { "review": input_review, "parameters": input_parameters_runasany}
10 | results := violation with input as input
11 | count(results) == 0
12 | }
13 | test_input_fsgroup_MustRunAs_allowed {
14 | input := { "review": input_review_with_fsgroup, "parameters": input_parameters_in_list_mustrunas}
15 | results := violation with input as input
16 | count(results) == 0
17 | }
18 | test_input_fsgroup_MustRunAs_not_allowed {
19 | input := { "review": input_review_with_fsgroup, "parameters": input_parameters_in_list_mustrunas_outofrange}
20 | results := violation with input as input
21 | count(results) > 0
22 | }
23 | test_input_no_fsgroup_MustRunAs_not_allowed {
24 | input := { "review": input_review, "parameters": input_parameters_in_list_mustrunas}
25 | results := violation with input as input
26 | count(results) > 0
27 | }
28 | test_input_fsgroup_MayRunAs_allowed {
29 | input := { "review": input_review_with_fsgroup, "parameters": input_parameters_in_list_mayrunas}
30 | results := violation with input as input
31 | count(results) == 0
32 | }
33 | test_input_fsgroup_MayRunAs_not_allowed {
34 | input := { "review": input_review_with_fsgroup, "parameters": input_parameters_in_list_mayrunas_outofrange}
35 | results := violation with input as input
36 | count(results) > 0
37 | }
38 |
39 | test_input_no_fsgroup_MayRunAs_not_allowed {
40 | input := { "review": input_review, "parameters": input_parameters_in_list_mayrunas}
41 | results := violation with input as input
42 | count(results) == 0
43 | }
44 |
45 | input_review = {
46 | "object": {
47 | "metadata": {
48 | "name": "nginx"
49 | },
50 | "spec": {
51 | "containers": input_containers_one,
52 | "volumes": input_volumes
53 | }
54 | }
55 | }
56 |
57 | input_review_with_fsgroup = {
58 | "object": {
59 | "metadata": {
60 | "name": "nginx"
61 | },
62 | "spec": {
63 | "securityContext": {
64 | "fsGroup": 2000
65 | },
66 | "containers": input_containers_one,
67 | "volumes": input_volumes
68 | }
69 | }
70 | }
71 |
72 | input_containers_one = [
73 | {
74 | "name": "nginx",
75 | "image": "nginx",
76 | "volumeMounts":[
77 | {
78 | "mountPath": "/cache",
79 | "name": "cache-volume"
80 | }]
81 | }]
82 |
83 | input_volumes = [
84 | {
85 | "name": "cache-volume",
86 | "emptyDir": {}
87 | }]
88 |
89 | input_parameters_runasany = {
90 | "rule": "RunAsAny"
91 | }
92 |
93 | input_parameters_in_list_mustrunas = {
94 | "rule": "MustRunAs",
95 | "ranges": [
96 | {
97 | "min": 1,
98 | "max": 2000
99 | }]
100 | }
101 | input_parameters_in_list_mustrunas_outofrange = {
102 | "rule": "MustRunAs",
103 | "ranges": [
104 | {
105 | "min": 1,
106 | "max": 1000
107 | }]
108 | }
109 | input_parameters_in_list_mayrunas = {
110 | "rule": "MayRunAs",
111 | "ranges": [
112 | {
113 | "min": 1,
114 | "max": 2000
115 | }]
116 | }
117 | input_parameters_in_list_mayrunas_outofrange = {
118 | "rule": "MayRunAs",
119 | "ranges": [
120 | {
121 | "min": 1,
122 | "max": 1000
123 | }]
124 | }
125 |
--------------------------------------------------------------------------------
/flexvolume-drivers/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spspflexvolumes
2 |
3 | has_field(object, field) = true {
4 | object[field]
5 | }
6 |
7 | test_input_flexvolume_allowed_all {
8 | input := { "review": input_review, "parameters": input_parameters_wildcard}
9 | results := violation with input as input
10 | count(results) == 0
11 | }
12 | test_input_no_flexvolume_is_allowed {
13 | input := { "review": input_review_no_flexvolume, "parameters": input_parameters_in_list}
14 | results := violation with input as input
15 | count(results) == 0
16 | }
17 | test_input_flexvolume_is_allowed {
18 | input := { "review": input_review_many, "parameters": input_parameters_in_list}
19 | results := violation with input as input
20 | count(results) == 0
21 | }
22 |
23 | test_input_flexvolume_not_allowed {
24 | input := { "review": input_review, "parameters": input_parameters_not_in_list}
25 | results := violation with input as input
26 | count(results) == 1
27 | }
28 | test_input_flexvolume_many_not_allowed {
29 | input := { "review": input_review_many, "parameters": input_parameters_not_in_list}
30 | results := violation with input as input
31 | count(results) == 1
32 | }
33 |
34 | input_review = {
35 | "object": {
36 | "metadata": {
37 | "name": "nginx"
38 | },
39 | "spec": {
40 | "containers": input_containers_one,
41 | "volumes": input_volumes
42 | }
43 | }
44 | }
45 |
46 | input_review_no_flexvolume = {
47 | "object": {
48 | "metadata": {
49 | "name": "nginx"
50 | },
51 | "spec": {
52 | "containers": input_containers_one,
53 | "volumes": []
54 | }
55 | }
56 | }
57 |
58 | input_review_many = {
59 | "object": {
60 | "metadata": {
61 | "name": "nginx"
62 | },
63 | "spec": {
64 | "containers": input_containers_many,
65 | "volumes": input_volumes_many
66 | }
67 | }
68 | }
69 |
70 | input_containers_one = [
71 | {
72 | "name": "nginx",
73 | "image": "nginx",
74 | "volumeMounts":[
75 | {
76 | "mountPath": "/cache",
77 | "name": "cache-volume"
78 | }]
79 | }]
80 |
81 | input_containers_many = [
82 | {
83 | "name": "nginx",
84 | "image": "nginx",
85 | "volumeMounts":[
86 | {
87 | "mountPath": "/cache",
88 | "name": "cache-volume",
89 | "readOnly": true
90 | }]
91 | },
92 | {
93 | "name": "nginx2",
94 | "image": "nginx",
95 | "volumeMounts":[
96 | {
97 | "mountPath": "/cache",
98 | "name": "cache-volume2",
99 | "readOnly": true
100 | }]
101 | }]
102 |
103 | input_volumes = [
104 | {
105 | "name": "cache-volume",
106 | "flexVolume": {
107 | "driver": "example/lvm"
108 | }
109 | }]
110 |
111 | input_volumes_many = [
112 | {
113 | "name": "cache-volume",
114 | "flexVolume": {
115 | "driver": "example/lvm"
116 | }
117 | },
118 | {
119 | "name": "cache-volume2",
120 | "flexVolume": {
121 | "driver": "example/cifs"
122 | }
123 | }]
124 |
125 | input_parameters_wildcard = {
126 | "allowedFlexVolumes": []
127 | }
128 |
129 | input_parameters_in_list = {
130 | "allowedFlexVolumes": [
131 | {
132 | "driver": "example/lvm"
133 | },
134 | {
135 | "driver": "example/cifs"
136 | }]
137 | }
138 |
139 | input_parameters_not_in_list = {
140 | "allowedFlexVolumes": [
141 | {
142 | "driver": "example/testdriver"
143 | },
144 | {
145 | "driver": "example/cifs"
146 | }]
147 | }
148 |
--------------------------------------------------------------------------------
/proc-mount/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spspprocmount
2 |
3 | test_input_container_not_proc_mount_allowed {
4 | input := { "review": input_review, "parameters": input_parameters_default}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 | test_input_container_proc_mount_not_allowed {
9 | input := { "review": input_review_unmasked, "parameters": input_parameters_default}
10 | results := violation with input as input
11 | count(results) > 0
12 | }
13 | test_input_container_many_not_proc_mount_allowed {
14 | input := { "review": input_review_many, "parameters": input_parameters_default}
15 | results := violation with input as input
16 | count(results) == 0
17 | }
18 | test_input_container_many_mixed_proc_mount_not_allowed {
19 | input := { "review": input_review_many_mixed, "parameters": input_parameters_default}
20 | results := violation with input as input
21 | count(results) > 0
22 | }
23 | test_input_container_many_mixed_proc_mount_not_allowed_two {
24 | input := { "review": input_review_many_mixed_two, "parameters": input_parameters_default}
25 | results := violation with input as input
26 | count(results) == 2
27 | }
28 |
29 | input_review = {
30 | "object": {
31 | "metadata": {
32 | "name": "nginx"
33 | },
34 | "spec": {
35 | "containers": input_containers_one
36 | }
37 | }
38 | }
39 |
40 | input_review_unmasked = {
41 | "object": {
42 | "metadata": {
43 | "name": "nginx"
44 | },
45 | "spec": {
46 | "containers": input_containers_one_unmasked
47 | }
48 | }
49 | }
50 |
51 | input_review_many = {
52 | "object": {
53 | "metadata": {
54 | "name": "nginx"
55 | },
56 | "spec": {
57 | "containers": input_containers_many,
58 | "initContainers": input_containers_one
59 | }
60 | }
61 | }
62 |
63 | input_review_many_mixed = {
64 | "object": {
65 | "metadata": {
66 | "name": "nginx"
67 | },
68 | "spec": {
69 | "containers": input_containers_many,
70 | "initContainers": input_containers_one_unmasked
71 | }
72 | }
73 | }
74 |
75 | input_review_many_mixed_two = {
76 | "object": {
77 | "metadata": {
78 | "name": "nginx"
79 | },
80 | "spec": {
81 | "containers": input_containers_many_mixed,
82 | "initContainers": input_containers_one_unmasked
83 | }
84 | }
85 | }
86 |
87 | input_containers_one = [
88 | {
89 | "name": "nginx",
90 | "image": "nginx",
91 | "securityContext": {
92 | "procMount": "Default"
93 | }
94 | }]
95 |
96 | input_containers_one_unmasked = [
97 | {
98 | "name": "nginx",
99 | "image": "nginx",
100 | "securityContext": {
101 | "procMount": "Unmasked"
102 | }
103 | }]
104 |
105 | input_containers_many = [
106 | {
107 | "name": "nginx",
108 | "image": "nginx",
109 | "securityContext": {
110 | "procMount": "Default"
111 | }
112 | },
113 | {
114 | "name": "nginx1",
115 | "image": "nginx"
116 | }]
117 |
118 | input_containers_many_mixed = [
119 | {
120 | "name": "nginx",
121 | "image": "nginx",
122 | "securityContext": {
123 | "procMount": "Default"
124 | }
125 | },
126 | {
127 | "name": "nginx1",
128 | "image": "nginx",
129 | "securityContext": {
130 | "procMount": "Unmasked"
131 | }
132 | }]
133 |
134 | input_parameters_default = {
135 | "procMount": "Default"
136 | }
137 |
138 | input_parameters_unmasked = {
139 | "procMount": "Unmasked"
140 | }
141 |
--------------------------------------------------------------------------------
/host-network-ports/src_test.rego:
--------------------------------------------------------------------------------
1 | package k8spsphostnetworkingports
2 |
3 | test_input_no_hostnetwork_no_port_is_allowed {
4 | input := { "review": input_review, "parameters": input_parameters_ports}
5 | results := violation with input as input
6 | count(results) == 0
7 | }
8 | test_input_no_hostnetwork_allowed_ports_is_allowed {
9 | input := { "review": input_review_no_hostnetwork_allowed_ports, "parameters": input_parameters_ports}
10 | results := violation with input as input
11 | count(results) == 0
12 | }
13 | test_input_no_hostnetwork_container_ports_not_allowed {
14 | input := { "review": input_review_no_hostnetwork_container_ports_outofrange, "parameters": input_parameters_ports}
15 | results := violation with input as input
16 | count(results) > 0
17 | }
18 | test_input_with_hostnetwork_is_allowed {
19 | input := { "review": input_review_with_hostnetwork_no_port, "parameters": input_parameters_ports}
20 | results := violation with input as input
21 | count(results) == 0
22 | }
23 | test_input_with_hostnetwork_constraint_no_hostnetwork_not_allowed {
24 | input := { "review": input_review_with_hostnetwork_no_port, "parameters": input_parameters_ports_no_hostnetwork}
25 | results := violation with input as input
26 | count(results) > 0
27 | }
28 | test_input_with_hostnetwork_container_ports_not_allowed {
29 | input := { "review": input_review_with_hostnetwork_port_outofrange, "parameters": input_parameters_ports}
30 | results := violation with input as input
31 | count(results) == 1
32 | }
33 |
34 | input_review = {
35 | "object": {
36 | "metadata": {
37 | "name": "nginx"
38 | },
39 | "spec": {
40 | "containers": input_containers_one
41 | }
42 | }
43 | }
44 |
45 | input_review_no_hostnetwork_allowed_ports = {
46 | "object": {
47 | "metadata": {
48 | "name": "nginx"
49 | },
50 | "spec": {
51 | "containers": input_containers_one_port
52 | }
53 | }
54 | }
55 |
56 | input_review_no_hostnetwork_container_ports_outofrange = {
57 | "object": {
58 | "metadata": {
59 | "name": "nginx"
60 | },
61 | "spec": {
62 | "containers": input_containers_one_port_outofrange
63 | }
64 | }
65 | }
66 |
67 | input_review_with_hostnetwork_no_port = {
68 | "object": {
69 | "metadata": {
70 | "name": "nginx"
71 | },
72 | "spec": {
73 | "hostNetwork": true,
74 | "containers": input_containers_one
75 | }
76 | }
77 | }
78 |
79 | input_review_with_hostnetwork_port_outofrange = {
80 | "object": {
81 | "metadata": {
82 | "name": "nginx"
83 | },
84 | "spec": {
85 | "hostNetwork": true,
86 | "containers": input_containers_many_port_outofrange,
87 | "initContainers": input_containers_one_port_outofrange
88 | }
89 | }
90 | }
91 | input_containers_one = [
92 | {
93 | "name": "nginx",
94 | "image": "nginx"
95 | }]
96 |
97 | input_containers_one_port = [
98 | {
99 | "name": "nginx",
100 | "image": "nginx",
101 | "ports": [{
102 | "containerPort": 80,
103 | "hostPort": 8080
104 | }]
105 | }]
106 | input_containers_one_port_outofrange = [
107 | {
108 | "name": "nginx",
109 | "image": "nginx",
110 | "ports": [{
111 | "containerPort": 80,
112 | "hostPort": 9001
113 | }]
114 | }]
115 |
116 | input_containers_many_port_outofrange = [
117 | {
118 | "name": "nginx",
119 | "image": "nginx",
120 | "ports": [{
121 | "containerPort": 80,
122 | "hostPort": 9001
123 | }]
124 | },
125 | {
126 | "name": "nginx1",
127 | "image": "nginx",
128 | "ports": [{
129 | "containerPort": 9200,
130 | "hostPort": 9200
131 | }]
132 | }]
133 |
134 | input_parameters_ports = {
135 | "hostNetwork": true,
136 | "min": 80,
137 | "max": 9000
138 | }
139 |
140 | input_parameters_ports_no_hostnetwork = {
141 | "min": 80,
142 | "max": 9000
143 | }
144 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # psp-gatekeeper-policies
2 |
3 | ⚠️⚠️⚠️ This repo has been donated to the CNCF [Open Policy Agent Gatekeeper](https://github.com/open-policy-agent/gatekeeper) project. You can find the same policies under [https://github.com/open-policy-agent/gatekeeper/tree/master/library/pod-security-policy/](https://github.com/open-policy-agent/gatekeeper/tree/master/library/pod-security-policy/). Feel free to open issues and PRs there!
4 |
5 | --------------
6 |
7 |
8 | This repo contains common policies needed in Pod Security Policy but implemented as Constraints and Constraint Templates with Gatekeeper.
9 |
10 | A [Pod Security Policy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) is a cluster-level resource that controls security
11 | sensitive aspects of the pod specification. The `PodSecurityPolicy` objects define a set of conditions that a pod must run with in order to be accepted into
12 | the system, as well as defaults for the related fields.
13 |
14 | [Gatekeeper](https://github.com/open-policy-agent/gatekeeper) is a customizable admission webhook for Kubernetes that enforces policies executed by the [Open Policy Agent (OPA)](https://www.openpolicyagent.org), a policy engine for Cloud Native environments hosted by CNCF.
15 |
16 | An adminstrator can control the following by setting the field in PSP or by deploying the corresponding Gatekeeper constraint and constraint templates:
17 |
18 | | Control Aspect | Field Names in PSP | Gatekeeper Constraint and Constraint Template |
19 | | ------------------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------- |
20 | | Running of privileged containers | `privileged` | [privileged-containers](../../tree/master/privileged-containers) |
21 | | Usage of host namespaces | `hostPID`, `hostIPC` | [host-namespaces](../../tree/master/host-namespaces) |
22 | | Usage of host networking and ports | `hostNetwork`, `hostPorts` | [host-network-ports](../../tree/master/host-network-ports) |
23 | | Usage of volume types | `volumes` | [volumes](../../tree/master/volumes) |
24 | | Usage of the host filesystem | `allowedHostPaths` | [host-filesystem](../../tree/master/host-filesystem) |
25 | | White list of Flexvolume drivers | `allowedFlexVolumes` | [flexvolume-drivers](../../tree/master/flexvolume-drivers) |
26 | | Allocating an FSGroup that owns the pod's volumes | `fsGroup` | [fsgroup](../../tree/master/fsgroup)\* |
27 | | Requiring the use of a read only root file system | `readOnlyRootFilesystem` | [read-only-root-filesystem](read-only-root-filesystem) |
28 | | The user and group IDs of the container | `runAsUser`, `runAsGroup`, `supplementalGroups` |
29 | | Restricting escalation to root privileges | `allowPrivilegeEscalation`, `defaultAllowPrivilegeEscalation` | [allow-privilege-escalation](allow-privilege-escalation) |
30 | | Linux capabilities | `defaultAddCapabilities`, `requiredDropCapabilities`, `allowedCapabilities` |
31 | | The SELinux context of the container | `seLinux` |
32 | | The Allowed Proc Mount types for the container | `allowedProcMountTypes` | [proc-mount](proc-mount) |
33 | | The AppArmor profile used by containers | annotations |
34 | | The seccomp profile used by containers | annotations |
35 | | The sysctl profile used by containers | `forbiddenSysctls`,`allowedUnsafeSysctls` | |
36 |
37 | \* For PSP rules that apply default value or mutations, Gatekeeper v3 currently cannot apply mutation.
38 |
--------------------------------------------------------------------------------