├── .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 | --------------------------------------------------------------------------------