diff --git a/artifacthub/library/general/requiredprobes/1.1.0/artifacthub-pkg.yml b/artifacthub/library/general/requiredprobes/1.1.0/artifacthub-pkg.yml
new file mode 100644
index 000000000..4f3f1deba
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/artifacthub-pkg.yml
@@ -0,0 +1,22 @@
+version: 1.1.0
+name: k8srequiredprobes
+displayName: Required Probes
+createdAt: "2023-01-04T23:20:17Z"
+description: Requires Pods to have readiness and/or liveness probes.
+digest: 1f871f3eb3e25749d3d6872736253ae2cd2dc394ed9b402bb1560cdd28613666
+license: Apache-2.0
+homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/requiredprobes
+keywords:
+ - gatekeeper
+ - open-policy-agent
+ - policies
+readme: |-
+ # Required Probes
+ Requires Pods to have readiness and/or liveness probes.
+install: |-
+ ### Usage
+ ```shell
+ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/general/requiredprobes/1.1.0/template.yaml
+ ```
+provider:
+ name: Gatekeeper Library
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/kustomization.yaml b/artifacthub/library/general/requiredprobes/1.1.0/kustomization.yaml
new file mode 100644
index 000000000..7d70d11b7
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/kustomization.yaml
@@ -0,0 +1,2 @@
+resources:
+ - template.yaml
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/constraint.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/constraint.yaml
new file mode 100644
index 000000000..d736aa4c0
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/constraint.yaml
@@ -0,0 +1,15 @@
+apiVersion: constraints.gatekeeper.sh/v1beta1
+kind: K8sRequiredProbes
+metadata:
+ name: must-have-probes-on-service
+spec:
+ enforcementAction: warn
+ match:
+ kinds:
+ - apiGroups: [""]
+ kinds: ["Pod"]
+ parameters:
+ onlyServices: true
+ probes: ["readinessProbe", "livenessProbe"]
+ probeTypes: ["tcpSocket", "httpGet", "exec"]
+ customViolationMessage: "See https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes for more info."
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_allowed_with_service.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_allowed_with_service.yaml
new file mode 100644
index 000000000..b333d912f
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_allowed_with_service.yaml
@@ -0,0 +1,27 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat
+spec:
+ containers:
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ name: tomcat-http
+ livenessProbe:
+ tcpSocket:
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_allowed_without_service.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_allowed_without_service.yaml
new file mode 100644
index 000000000..2c8f3a84f
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_allowed_without_service.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat-no-svc
+ second-label: "example"
+spec:
+ containers:
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_disallowed_with_service.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_disallowed_with_service.yaml
new file mode 100644
index 000000000..662741368
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/example_disallowed_with_service.yaml
@@ -0,0 +1,35 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat
+ second-label: "example"
+spec:
+ containers:
+ - name: nginx-1
+ image: nginx:1.7.9
+ ports:
+ - containerPort: 80
+ livenessProbe:
+ # tcpSocket:
+ # port: 80
+ # initialDelaySeconds: 5
+ # periodSeconds: 10
+ volumeMounts:
+ - mountPath: /tmp/cache
+ name: cache-volume
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ name: tomcat-http
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/inventory.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/inventory.yaml
new file mode 100644
index 000000000..b32e5c6a1
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes-on-service/inventory.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: tomcat-service
+ namespace: default
+spec:
+ selector:
+ app.kubernetes.io/name: tomcat
+ ports:
+ - name: name-of-service-port
+ protocol: TCP
+ port: 80
+ targetPort: tomcat-http
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/constraint.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/constraint.yaml
new file mode 100644
index 000000000..f4d526b16
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/constraint.yaml
@@ -0,0 +1,14 @@
+apiVersion: constraints.gatekeeper.sh/v1beta1
+kind: K8sRequiredProbes
+metadata:
+ name: must-have-probes
+spec:
+ enforcementAction: warn
+ match:
+ kinds:
+ - apiGroups: [""]
+ kinds: ["Pod"]
+ parameters:
+ onlyServices: false
+ probes: ["readinessProbe", "livenessProbe"]
+ probeTypes: ["tcpSocket", "httpGet", "exec"]
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_allowed.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_allowed.yaml
new file mode 100644
index 000000000..4248b67dd
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_allowed.yaml
@@ -0,0 +1,23 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+spec:
+ containers:
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ livenessProbe:
+ tcpSocket:
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_disallowed.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_disallowed.yaml
new file mode 100644
index 000000000..6db251904
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_disallowed.yaml
@@ -0,0 +1,30 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+spec:
+ containers:
+ - name: nginx-1
+ image: nginx:1.7.9
+ ports:
+ - containerPort: 80
+ livenessProbe:
+ # tcpSocket:
+ # port: 80
+ # initialDelaySeconds: 5
+ # periodSeconds: 10
+ volumeMounts:
+ - mountPath: /tmp/cache
+ name: cache-volume
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_disallowed2.yaml b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_disallowed2.yaml
new file mode 100644
index 000000000..6e0536487
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/samples/must-have-probes/example_disallowed2.yaml
@@ -0,0 +1,41 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod2
+spec:
+ containers:
+ - name: nginx-1
+ image: nginx:1.7.9
+ ports:
+ - containerPort: 80
+ readinessProbe:
+ # httpGet:
+ # path: /
+ # port: 80
+ # initialDelaySeconds: 5
+ # periodSeconds: 10
+ livenessProbe:
+ tcpSocket:
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumeMounts:
+ - mountPath: /tmp/cache
+ name: cache-volume
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ # livenessProbe:
+ # tcpSocket:
+ # port: 8080
+ # initialDelaySeconds: 5
+ # periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/suite.yaml b/artifacthub/library/general/requiredprobes/1.1.0/suite.yaml
new file mode 100644
index 000000000..4aab97420
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/suite.yaml
@@ -0,0 +1,43 @@
+kind: Suite
+apiVersion: test.gatekeeper.sh/v1alpha1
+metadata:
+ name: containerprobes
+tests:
+- name: container-probes
+ template: template.yaml
+ constraint: samples/must-have-probes/constraint.yaml
+ cases:
+ - name: example-allowed
+ object: samples/must-have-probes/example_allowed.yaml
+ assertions:
+ - violations: no
+ - name: example-disallowed
+ object: samples/must-have-probes/example_disallowed.yaml
+ assertions:
+ - violations: yes
+ - name: example-disallowed2
+ object: samples/must-have-probes/example_disallowed2.yaml
+ assertions:
+ - violations: yes
+- name: container-probes-only-services
+ template: template.yaml
+ constraint: samples/must-have-probes-on-service/constraint.yaml
+ cases:
+ - name: example-allowed-without-service
+ object: samples/must-have-probes-on-service/example_allowed_without_service.yaml
+ inventory:
+ - samples/must-have-probes-on-service/inventory.yaml
+ assertions:
+ - violations: no
+ - name: example-allowed-with-service
+ object: samples/must-have-probes-on-service/example_allowed_with_service.yaml
+ inventory:
+ - samples/must-have-probes-on-service/inventory.yaml
+ assertions:
+ - violations: no
+ - name: example-disallowed-with-service
+ object: samples/must-have-probes-on-service/example_disallowed_with_service.yaml
+ inventory:
+ - samples/must-have-probes-on-service/inventory.yaml
+ assertions:
+ - violations: yes
diff --git a/artifacthub/library/general/requiredprobes/1.1.0/template.yaml b/artifacthub/library/general/requiredprobes/1.1.0/template.yaml
new file mode 100644
index 000000000..b61c3bfdc
--- /dev/null
+++ b/artifacthub/library/general/requiredprobes/1.1.0/template.yaml
@@ -0,0 +1,101 @@
+apiVersion: templates.gatekeeper.sh/v1
+kind: ConstraintTemplate
+metadata:
+ name: k8srequiredprobes
+ annotations:
+ metadata.gatekeeper.sh/title: "Required Probes"
+ metadata.gatekeeper.sh/version: 1.1.0
+ metadata.gatekeeper.sh/requiresSyncData: |
+ "[
+ [
+ {
+ "groups":[""],
+ "versions": ["v1"],
+ "kinds": ["Service"]
+ }
+ ]
+ ]"
+ description: Requires Pods to have readiness and/or liveness probes.
+spec:
+ crd:
+ spec:
+ names:
+ kind: K8sRequiredProbes
+ validation:
+ openAPIV3Schema:
+ type: object
+ properties:
+ onlyServices:
+ description: "Only apply to pods that are selected by a service"
+ type: boolean
+ probes:
+ description: "A list of probes that are required (ex: `readinessProbe`)"
+ type: array
+ items:
+ type: string
+ probeTypes:
+ description: "The probe must define a field listed in `probeType` in order to satisfy the constraint (ex. `tcpSocket` satisfies `['tcpSocket', 'exec']`)"
+ type: array
+ items:
+ type: string
+ customViolationMessage:
+ type: string
+ description: >-
+ Custom error message generated by a violation that is appended to the standard violation message
+ targets:
+ - target: admission.k8s.gatekeeper.sh
+ rego: |
+ package k8srequiredprobes
+
+ probe_type_set = probe_types {
+ probe_types := {type | type := input.parameters.probeTypes[_]}
+ }
+
+ violation[{"msg": msg}] {
+ not input.parameters.onlyServices
+ container := input.review.object.spec.containers[_]
+ probe := input.parameters.probes[_]
+ probe_is_missing(container, probe)
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v>. %v", [container.name, input.review.kind.kind, probe, custom_msg]), " ")
+ }
+
+ violation[{"msg": msg}] {
+ input.parameters.onlyServices
+ container := input.review.object.spec.containers[_]
+ probe := input.parameters.probes[_]
+ probe_is_missing(container, probe)
+
+ obj := input.review.object
+ svc := data.inventory.namespace[obj.metadata.namespace]["v1"]["Service"][_]
+ matchLabels := { [label, value] | some label; value := svc.spec.selector[label] }
+ labels := { [label, value] | some label; value := obj.metadata.labels[label] }
+ count(matchLabels - labels) == 0
+ matching_ports := [p | p := svc.spec.ports[_].targetPort; has_port(p, container)]
+ count(matching_ports) > 0
+
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v> and is selected by service <%v> with targetPort(s) %v. %v", [container.name, input.review.kind.kind, probe, svc.metadata.name, matching_ports, custom_msg]), " ")
+ }
+
+ has_port(targetPort, container){
+ targetPort == container.ports[_].containerPort
+ }
+
+ has_port(targetPort, container){
+ targetPort == container.ports[_].name
+ }
+
+ probe_is_missing(ctr, probe) = true {
+ not ctr[probe]
+ }
+
+ probe_is_missing(ctr, probe) = true {
+ probe_field_empty(ctr, probe)
+ }
+
+ probe_field_empty(ctr, probe) = true {
+ probe_fields := {field | ctr[probe][field]}
+ diff_fields := probe_type_set - probe_fields
+ count(diff_fields) == count(probe_type_set)
+ }
diff --git a/library/general/requiredprobes/samples/must-have-probes-on-service/constraint.yaml b/library/general/requiredprobes/samples/must-have-probes-on-service/constraint.yaml
new file mode 100644
index 000000000..d736aa4c0
--- /dev/null
+++ b/library/general/requiredprobes/samples/must-have-probes-on-service/constraint.yaml
@@ -0,0 +1,15 @@
+apiVersion: constraints.gatekeeper.sh/v1beta1
+kind: K8sRequiredProbes
+metadata:
+ name: must-have-probes-on-service
+spec:
+ enforcementAction: warn
+ match:
+ kinds:
+ - apiGroups: [""]
+ kinds: ["Pod"]
+ parameters:
+ onlyServices: true
+ probes: ["readinessProbe", "livenessProbe"]
+ probeTypes: ["tcpSocket", "httpGet", "exec"]
+ customViolationMessage: "See https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes for more info."
diff --git a/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_with_service.yaml b/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_with_service.yaml
new file mode 100644
index 000000000..b333d912f
--- /dev/null
+++ b/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_with_service.yaml
@@ -0,0 +1,27 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat
+spec:
+ containers:
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ name: tomcat-http
+ livenessProbe:
+ tcpSocket:
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_without_service.yaml b/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_without_service.yaml
new file mode 100644
index 000000000..2c8f3a84f
--- /dev/null
+++ b/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_without_service.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat-no-svc
+ second-label: "example"
+spec:
+ containers:
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/library/general/requiredprobes/samples/must-have-probes-on-service/example_disallowed_with_service.yaml b/library/general/requiredprobes/samples/must-have-probes-on-service/example_disallowed_with_service.yaml
new file mode 100644
index 000000000..662741368
--- /dev/null
+++ b/library/general/requiredprobes/samples/must-have-probes-on-service/example_disallowed_with_service.yaml
@@ -0,0 +1,35 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat
+ second-label: "example"
+spec:
+ containers:
+ - name: nginx-1
+ image: nginx:1.7.9
+ ports:
+ - containerPort: 80
+ livenessProbe:
+ # tcpSocket:
+ # port: 80
+ # initialDelaySeconds: 5
+ # periodSeconds: 10
+ volumeMounts:
+ - mountPath: /tmp/cache
+ name: cache-volume
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ name: tomcat-http
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
diff --git a/library/general/requiredprobes/samples/must-have-probes-on-service/inventory.yaml b/library/general/requiredprobes/samples/must-have-probes-on-service/inventory.yaml
new file mode 100644
index 000000000..b32e5c6a1
--- /dev/null
+++ b/library/general/requiredprobes/samples/must-have-probes-on-service/inventory.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: tomcat-service
+ namespace: default
+spec:
+ selector:
+ app.kubernetes.io/name: tomcat
+ ports:
+ - name: name-of-service-port
+ protocol: TCP
+ port: 80
+ targetPort: tomcat-http
diff --git a/library/general/requiredprobes/samples/must-have-probes/constraint.yaml b/library/general/requiredprobes/samples/must-have-probes/constraint.yaml
index 84fde016a..f4d526b16 100644
--- a/library/general/requiredprobes/samples/must-have-probes/constraint.yaml
+++ b/library/general/requiredprobes/samples/must-have-probes/constraint.yaml
@@ -3,10 +3,12 @@ kind: K8sRequiredProbes
metadata:
name: must-have-probes
spec:
+ enforcementAction: warn
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
+ onlyServices: false
probes: ["readinessProbe", "livenessProbe"]
probeTypes: ["tcpSocket", "httpGet", "exec"]
diff --git a/library/general/requiredprobes/suite.yaml b/library/general/requiredprobes/suite.yaml
index 379e77563..4aab97420 100644
--- a/library/general/requiredprobes/suite.yaml
+++ b/library/general/requiredprobes/suite.yaml
@@ -1,9 +1,9 @@
kind: Suite
apiVersion: test.gatekeeper.sh/v1alpha1
metadata:
- name: requiredprobes
+ name: containerprobes
tests:
-- name: block-endpoint-default-role
+- name: container-probes
template: template.yaml
constraint: samples/must-have-probes/constraint.yaml
cases:
@@ -19,3 +19,25 @@ tests:
object: samples/must-have-probes/example_disallowed2.yaml
assertions:
- violations: yes
+- name: container-probes-only-services
+ template: template.yaml
+ constraint: samples/must-have-probes-on-service/constraint.yaml
+ cases:
+ - name: example-allowed-without-service
+ object: samples/must-have-probes-on-service/example_allowed_without_service.yaml
+ inventory:
+ - samples/must-have-probes-on-service/inventory.yaml
+ assertions:
+ - violations: no
+ - name: example-allowed-with-service
+ object: samples/must-have-probes-on-service/example_allowed_with_service.yaml
+ inventory:
+ - samples/must-have-probes-on-service/inventory.yaml
+ assertions:
+ - violations: no
+ - name: example-disallowed-with-service
+ object: samples/must-have-probes-on-service/example_disallowed_with_service.yaml
+ inventory:
+ - samples/must-have-probes-on-service/inventory.yaml
+ assertions:
+ - violations: yes
diff --git a/library/general/requiredprobes/sync.yaml b/library/general/requiredprobes/sync.yaml
new file mode 100644
index 000000000..433294b0f
--- /dev/null
+++ b/library/general/requiredprobes/sync.yaml
@@ -0,0 +1,11 @@
+apiVersion: config.gatekeeper.sh/v1alpha1
+kind: Config
+metadata:
+ name: config
+ namespace: "gatekeeper-system"
+spec:
+ sync:
+ syncOnly:
+ - group: ""
+ version: "v1"
+ kind: "Service"
diff --git a/library/general/requiredprobes/template.yaml b/library/general/requiredprobes/template.yaml
index 26417b101..b61c3bfdc 100644
--- a/library/general/requiredprobes/template.yaml
+++ b/library/general/requiredprobes/template.yaml
@@ -4,7 +4,17 @@ metadata:
name: k8srequiredprobes
annotations:
metadata.gatekeeper.sh/title: "Required Probes"
- metadata.gatekeeper.sh/version: 1.0.0
+ metadata.gatekeeper.sh/version: 1.1.0
+ metadata.gatekeeper.sh/requiresSyncData: |
+ "[
+ [
+ {
+ "groups":[""],
+ "versions": ["v1"],
+ "kinds": ["Service"]
+ }
+ ]
+ ]"
description: Requires Pods to have readiness and/or liveness probes.
spec:
crd:
@@ -15,6 +25,9 @@ spec:
openAPIV3Schema:
type: object
properties:
+ onlyServices:
+ description: "Only apply to pods that are selected by a service"
+ type: boolean
probes:
description: "A list of probes that are required (ex: `readinessProbe`)"
type: array
@@ -25,6 +38,10 @@ spec:
type: array
items:
type: string
+ customViolationMessage:
+ type: string
+ description: >-
+ Custom error message generated by a violation that is appended to the standard violation message
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
@@ -35,10 +52,38 @@ spec:
}
violation[{"msg": msg}] {
+ not input.parameters.onlyServices
container := input.review.object.spec.containers[_]
probe := input.parameters.probes[_]
probe_is_missing(container, probe)
- msg := get_violation_message(container, input.review, probe)
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v>. %v", [container.name, input.review.kind.kind, probe, custom_msg]), " ")
+ }
+
+ violation[{"msg": msg}] {
+ input.parameters.onlyServices
+ container := input.review.object.spec.containers[_]
+ probe := input.parameters.probes[_]
+ probe_is_missing(container, probe)
+
+ obj := input.review.object
+ svc := data.inventory.namespace[obj.metadata.namespace]["v1"]["Service"][_]
+ matchLabels := { [label, value] | some label; value := svc.spec.selector[label] }
+ labels := { [label, value] | some label; value := obj.metadata.labels[label] }
+ count(matchLabels - labels) == 0
+ matching_ports := [p | p := svc.spec.ports[_].targetPort; has_port(p, container)]
+ count(matching_ports) > 0
+
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v> and is selected by service <%v> with targetPort(s) %v. %v", [container.name, input.review.kind.kind, probe, svc.metadata.name, matching_ports, custom_msg]), " ")
+ }
+
+ has_port(targetPort, container){
+ targetPort == container.ports[_].containerPort
+ }
+
+ has_port(targetPort, container){
+ targetPort == container.ports[_].name
}
probe_is_missing(ctr, probe) = true {
@@ -54,7 +99,3 @@ spec:
diff_fields := probe_type_set - probe_fields
count(diff_fields) == count(probe_type_set)
}
-
- get_violation_message(container, review, probe) = msg {
- msg := sprintf("Container <%v> in your <%v> <%v> has no <%v>", [container.name, review.kind.kind, review.object.metadata.name, probe])
- }
diff --git a/src/general/requiredprobes/constraint.tmpl b/src/general/requiredprobes/constraint.tmpl
index 6b81857dc..2d18af595 100644
--- a/src/general/requiredprobes/constraint.tmpl
+++ b/src/general/requiredprobes/constraint.tmpl
@@ -4,7 +4,17 @@ metadata:
name: k8srequiredprobes
annotations:
metadata.gatekeeper.sh/title: "Required Probes"
- metadata.gatekeeper.sh/version: 1.0.0
+ metadata.gatekeeper.sh/version: 1.1.0
+ metadata.gatekeeper.sh/requiresSyncData: |
+ "[
+ [
+ {
+ "groups":[""],
+ "versions": ["v1"],
+ "kinds": ["Service"]
+ }
+ ]
+ ]"
description: Requires Pods to have readiness and/or liveness probes.
spec:
crd:
@@ -15,6 +25,9 @@ spec:
openAPIV3Schema:
type: object
properties:
+ onlyServices:
+ description: "Only apply to pods that are selected by a service"
+ type: boolean
probes:
description: "A list of probes that are required (ex: `readinessProbe`)"
type: array
@@ -25,6 +38,10 @@ spec:
type: array
items:
type: string
+ customViolationMessage:
+ type: string
+ description: >-
+ Custom error message generated by a violation that is appended to the standard violation message
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
diff --git a/src/general/requiredprobes/src.rego b/src/general/requiredprobes/src.rego
index 532b036d7..9b09e3f24 100644
--- a/src/general/requiredprobes/src.rego
+++ b/src/general/requiredprobes/src.rego
@@ -5,10 +5,38 @@ probe_type_set = probe_types {
}
violation[{"msg": msg}] {
+ not input.parameters.onlyServices
container := input.review.object.spec.containers[_]
probe := input.parameters.probes[_]
probe_is_missing(container, probe)
- msg := get_violation_message(container, input.review, probe)
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v>. %v", [container.name, input.review.kind.kind, probe, custom_msg]), " ")
+}
+
+violation[{"msg": msg}] {
+ input.parameters.onlyServices
+ container := input.review.object.spec.containers[_]
+ probe := input.parameters.probes[_]
+ probe_is_missing(container, probe)
+
+ obj := input.review.object
+ svc := data.inventory.namespace[obj.metadata.namespace]["v1"]["Service"][_]
+ matchLabels := { [label, value] | some label; value := svc.spec.selector[label] }
+ labels := { [label, value] | some label; value := obj.metadata.labels[label] }
+ count(matchLabels - labels) == 0
+ matching_ports := [p | p := svc.spec.ports[_].targetPort; has_port(p, container)]
+ count(matching_ports) > 0
+
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v> and is selected by service <%v> with targetPort(s) %v. %v", [container.name, input.review.kind.kind, probe, svc.metadata.name, matching_ports, custom_msg]), " ")
+}
+
+has_port(targetPort, container){
+ targetPort == container.ports[_].containerPort
+}
+
+has_port(targetPort, container){
+ targetPort == container.ports[_].name
}
probe_is_missing(ctr, probe) = true {
@@ -24,7 +52,3 @@ probe_field_empty(ctr, probe) = true {
diff_fields := probe_type_set - probe_fields
count(diff_fields) == count(probe_type_set)
}
-
-get_violation_message(container, review, probe) = msg {
- msg := sprintf("Container <%v> in your <%v> <%v> has no <%v>", [container.name, review.kind.kind, review.object.metadata.name, probe])
-}
diff --git a/src/general/requiredprobes/src_test.rego b/src/general/requiredprobes/src_test.rego
index a860b2e46..02eff17d2 100644
--- a/src/general/requiredprobes/src_test.rego
+++ b/src/general/requiredprobes/src_test.rego
@@ -335,6 +335,60 @@ test_two_ctrs_empty_liveness_in_ctr_two_both_empty_probes_in_ctr_one {
count(results) == 3
}
+test_one_ctr_readiness_violation_with_svc_port_name {
+ kind := kinds[_]
+ input := {"review": review([{"name": "my-container1","image": "my-image:latest", "ports": [{ "name": "http", "containerPort": "8080"}], "livenessProbe": {"tcpSocket": {"port":80}}}]),
+ "parameters": parameters_only_svc}
+ inv := inv_svc({"app.kubernetes.io/name": "test"}, [{ "name": "name-of-service-port", "port": "80", "targetPort": "http"}])
+ results := violation with input as input with data.inventory as inv
+ count(results) == 1
+}
+
+test_one_ctr_readiness_violation_with_svc_port_num {
+ kind := kinds[_]
+ input := {"review": review([{"name": "my-container1","image": "my-image:latest", "ports": [{ "name": "http", "containerPort": "8080"}], "livenessProbe": {"tcpSocket": {"port":8080}}}]),
+ "parameters": parameters_only_svc}
+ inv := inv_svc({"app.kubernetes.io/name": "test"}, [{ "name": "name-of-service-port", "port": "80", "targetPort": "8080"}])
+ results := violation with input as input with data.inventory as inv
+ count(results) == 1
+}
+
+test_one_ctr_readiness_violation_with_svc_multiple_port_num {
+ kind := kinds[_]
+ input := {"review": review([{"name": "my-container1","image": "my-image:latest", "ports": [{ "name": "http", "containerPort": "8080"}, { "name": "https", "containerPort": "8443"}], "livenessProbe": {"tcpSocket": {"port":8080}}}]),
+ "parameters": parameters_only_svc}
+ inv := inv_svc({"app.kubernetes.io/name": "test"}, [{ "name": "name-of-service-port", "port": "80", "targetPort": "8080"}, { "name": "name-of-service-port", "port": "443", "targetPort": "8443"}])
+ results := violation with input as input with data.inventory as inv
+ count(results) == 1
+}
+
+test_one_ctr_no_violation_with_svc_port_name {
+ kind := kinds[_]
+ input := {"review": review([{"name": "my-container1","image": "my-image:latest", "ports": [{ "name": "http", "containerPort": "8080"}], "readinessProbe": {"tcpSocket": {"port":8080}}, "livenessProbe": {"tcpSocket": {"port":8080}}}]),
+ "parameters": parameters_only_svc}
+ inv := inv_svc({"app.kubernetes.io/name": "test"}, [{ "name": "name-of-service-port", "port": "80", "targetPort": "http"}])
+ results := violation with input as input with data.inventory as inv
+ count(results) == 0
+}
+
+test_one_ctr_no_violation_with_svc_port_num {
+ kind := kinds[_]
+ input := {"review": review([{"name": "my-container1","image": "my-image:latest", "ports": [{ "name": "http", "containerPort": "8080"}], "readinessProbe": {"tcpSocket": {"port":8080}}, "livenessProbe": {"tcpSocket": {"port":8080}}}]),
+ "parameters": parameters_only_svc}
+ inv := inv_svc({"app.kubernetes.io/name": "test"}, [{ "name": "name-of-service-port", "port": "80", "targetPort": "8080"}])
+ results := violation with input as input with data.inventory as inv
+ count(results) == 0
+}
+
+test_one_ctr_missing_both_no_violation_without_svc_port_num {
+ kind := kinds[_]
+ input := {"review": review([{"name": "my-container1","image": "my-image:latest", "ports": [{ "name": "http", "containerPort": "8080"}]}]),
+ "parameters": parameters_only_svc}
+ inv := inv_svc({"app.kubernetes.io/name": "non-matching-pod-selector"}, [{ "name": "name-of-service-port", "port": "80", "targetPort": "8080"}])
+ results := violation with input as input with data.inventory as inv
+ count(results) == 0
+}
+
review(containers) = obj {
obj = {
"kind": {
@@ -342,14 +396,44 @@ review(containers) = obj {
},
"object": {
"metadata": {
- "name": "some-name"
+ "name": "some-name",
+ "namespace": namespace,
+ "labels": {
+ "app.kubernetes.io/name": "test"
+ }
},
"spec": {
- "containers":containers
+ "containers": containers
}
}
}
}
+svc_out(selector, ports) = output {
+ output := {
+ "apiVersion": "v1",
+ "kind": "Service",
+ "metadata": {
+ "name": "example-service",
+ "namespace": namespace,
+ },
+ "spec": {
+ "selector": selector,
+ "ports": ports,
+ },
+ }
+}
+
+inventory(obj) = output {
+ output := {"namespace": {namespace: {obj.apiVersion: {obj.kind: [obj]}}}}
+}
+
+inv_svc(selector, ports) = output {
+ svc = svc_out(selector, ports)
+ output := inventory(svc)
+}
+
+namespace := "default"
parameters = {"probes": ["readinessProbe", "livenessProbe"], "probeTypes": ["tcpSocket", "httpGet", "exec"]}
+parameters_only_svc = {"onlyServices": true, "probes": ["readinessProbe", "livenessProbe"], "probeTypes": ["tcpSocket", "httpGet", "exec"]}
kinds = ["Pod"]
diff --git a/website/docs/requiredprobes.md b/website/docs/requiredprobes.md
index a09a05930..4516a17d3 100644
--- a/website/docs/requiredprobes.md
+++ b/website/docs/requiredprobes.md
@@ -16,7 +16,17 @@ metadata:
name: k8srequiredprobes
annotations:
metadata.gatekeeper.sh/title: "Required Probes"
- metadata.gatekeeper.sh/version: 1.0.0
+ metadata.gatekeeper.sh/version: 1.1.0
+ metadata.gatekeeper.sh/requiresSyncData: |
+ "[
+ [
+ {
+ "groups":[""],
+ "versions": ["v1"],
+ "kinds": ["Service"]
+ }
+ ]
+ ]"
description: Requires Pods to have readiness and/or liveness probes.
spec:
crd:
@@ -27,6 +37,9 @@ spec:
openAPIV3Schema:
type: object
properties:
+ onlyServices:
+ description: "Only apply to pods that are selected by a service"
+ type: boolean
probes:
description: "A list of probes that are required (ex: `readinessProbe`)"
type: array
@@ -37,6 +50,10 @@ spec:
type: array
items:
type: string
+ customViolationMessage:
+ type: string
+ description: >-
+ Custom error message generated by a violation that is appended to the standard violation message
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
@@ -47,10 +64,38 @@ spec:
}
violation[{"msg": msg}] {
+ not input.parameters.onlyServices
container := input.review.object.spec.containers[_]
probe := input.parameters.probes[_]
probe_is_missing(container, probe)
- msg := get_violation_message(container, input.review, probe)
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v>. %v", [container.name, input.review.kind.kind, probe, custom_msg]), " ")
+ }
+
+ violation[{"msg": msg}] {
+ input.parameters.onlyServices
+ container := input.review.object.spec.containers[_]
+ probe := input.parameters.probes[_]
+ probe_is_missing(container, probe)
+
+ obj := input.review.object
+ svc := data.inventory.namespace[obj.metadata.namespace]["v1"]["Service"][_]
+ matchLabels := { [label, value] | some label; value := svc.spec.selector[label] }
+ labels := { [label, value] | some label; value := obj.metadata.labels[label] }
+ count(matchLabels - labels) == 0
+ matching_ports := [p | p := svc.spec.ports[_].targetPort; has_port(p, container)]
+ count(matching_ports) > 0
+
+ custom_msg := object.get(input.parameters, "customViolationMessage", "")
+ msg := trim(sprintf("Container <%v> in this <%v> has no <%v> and is selected by service <%v> with targetPort(s) %v. %v", [container.name, input.review.kind.kind, probe, svc.metadata.name, matching_ports, custom_msg]), " ")
+ }
+
+ has_port(targetPort, container){
+ targetPort == container.ports[_].containerPort
+ }
+
+ has_port(targetPort, container){
+ targetPort == container.ports[_].name
}
probe_is_missing(ctr, probe) = true {
@@ -67,10 +112,6 @@ spec:
count(diff_fields) == count(probe_type_set)
}
- get_violation_message(container, review, probe) = msg {
- msg := sprintf("Container <%v> in your <%v> <%v> has no <%v>", [container.name, review.kind.kind, review.object.metadata.name, probe])
- }
-
```
### Usage
@@ -79,7 +120,7 @@ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-
```
## Examples
-block-endpoint-default-role
+container-probes
constraint
@@ -90,11 +131,13 @@ kind: K8sRequiredProbes
metadata:
name: must-have-probes
spec:
+ enforcementAction: warn
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
+ onlyServices: false
probes: ["readinessProbe", "livenessProbe"]
probeTypes: ["tcpSocket", "httpGet", "exec"]
@@ -246,4 +289,160 @@ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-
+
+container-probes-only-services
+
+
+constraint
+
+```yaml
+apiVersion: constraints.gatekeeper.sh/v1beta1
+kind: K8sRequiredProbes
+metadata:
+ name: must-have-probes-on-service
+spec:
+ enforcementAction: warn
+ match:
+ kinds:
+ - apiGroups: [""]
+ kinds: ["Pod"]
+ parameters:
+ onlyServices: true
+ probes: ["readinessProbe", "livenessProbe"]
+ probeTypes: ["tcpSocket", "httpGet", "exec"]
+ customViolationMessage: "See https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes for more info."
+
+```
+
+Usage
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredprobes/samples/must-have-probes-on-service/constraint.yaml
+```
+
+
+
+
+example-allowed-without-service
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat-no-svc
+ second-label: "example"
+spec:
+ containers:
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
+
+```
+
+Usage
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_without_service.yaml
+```
+
+
+
+example-allowed-with-service
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat
+spec:
+ containers:
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ name: tomcat-http
+ livenessProbe:
+ tcpSocket:
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
+
+```
+
+Usage
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredprobes/samples/must-have-probes-on-service/example_allowed_with_service.yaml
+```
+
+
+
+example-disallowed-with-service
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-pod1
+ namespace: default
+ labels:
+ app.kubernetes.io/name: tomcat
+ second-label: "example"
+spec:
+ containers:
+ - name: nginx-1
+ image: nginx:1.7.9
+ ports:
+ - containerPort: 80
+ livenessProbe:
+ # tcpSocket:
+ # port: 80
+ # initialDelaySeconds: 5
+ # periodSeconds: 10
+ volumeMounts:
+ - mountPath: /tmp/cache
+ name: cache-volume
+ - name: tomcat
+ image: tomcat
+ ports:
+ - containerPort: 8080
+ name: tomcat-http
+ readinessProbe:
+ tcpSocket:
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ volumes:
+ - name: cache-volume
+ emptyDir: {}
+
+```
+
+Usage
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredprobes/samples/must-have-probes-on-service/example_disallowed_with_service.yaml
+```
+
+
+
+
\ No newline at end of file