From 3120da7d271171b5973df17ee203d8d455999e99 Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Thu, 4 Apr 2024 03:46:58 +0000 Subject: [PATCH 1/7] Example of a VAP constraint template: allow-privilege-escalation Signed-off-by: Max Smythe --- .../allow-privilege-escalation/template.yaml | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/library/pod-security-policy/allow-privilege-escalation/template.yaml b/library/pod-security-policy/allow-privilege-escalation/template.yaml index a7f4694a2..aa7e3489c 100644 --- a/library/pod-security-policy/allow-privilege-escalation/template.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/template.yaml @@ -36,6 +36,35 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh + code: + - engine: K8sNativeValidation + source: + variables: + - name: containers + expression: 'has(object.spec.containers) ? object.spec.containers : []' + - name: initContainers + expression: 'has(object.spec.initContainers) ? object.spec.initContainers : []' + - name: ephemeralContainers + expression: 'has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : []' + - name: exemptImages + expression: | + !has(variables.params.exemptImages) ? [] : + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + // we can use the regex substitution because docker images do not allow "." as a valid character + variables.params.exemptImages.exists(exemption, container.image == exemption || (exemption.endsWith("*") && string(container.image).matches("^" + string(variables.params.exemptImages).replace("*", ".*") + "$"))) + ) + - name: badContainers + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + !(container.image in variables.exemptImages) && ( + !has(container.securityContext) || + !has(container.securityContext.allowPrivilegeEscalation) || + container.securityContext.allowPrivilegeEscalation != false + ) + ) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + messageExpression: '"Privilege escalation container is not allowed: " + variables.badContainers.map(c, c.image).join(", ")' rego: | package k8spspallowprivilegeescalationcontainer From 91d55d6bbc06a1c8671f5a83f31bf4d55bb3ffb5 Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Thu, 9 May 2024 21:39:01 -0700 Subject: [PATCH 2/7] Update library/pod-security-policy/allow-privilege-escalation/template.yaml Co-authored-by: Rita Zhang Signed-off-by: Max Smythe --- .../allow-privilege-escalation/template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/pod-security-policy/allow-privilege-escalation/template.yaml b/library/pod-security-policy/allow-privilege-escalation/template.yaml index aa7e3489c..5caaf14ca 100644 --- a/library/pod-security-policy/allow-privilege-escalation/template.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/template.yaml @@ -56,7 +56,7 @@ spec: - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, - !(container.image in variables.exemptImages) && ( + !(container in variables.exemptImages) && ( !has(container.securityContext) || !has(container.securityContext.allowPrivilegeEscalation) || container.securityContext.allowPrivilegeEscalation != false From 04ff9dec528a50d7f6535eaf884e6195189756ed Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Fri, 10 May 2024 04:43:24 +0000 Subject: [PATCH 3/7] Fix regex parsing Signed-off-by: Max Smythe --- .../psp-allow-privilege-escalation-container/constraint.yaml | 2 ++ .../allow-privilege-escalation/template.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/constraint.yaml b/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/constraint.yaml index fdc05a1c0..df3f0d823 100644 --- a/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/constraint.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/constraint.yaml @@ -7,3 +7,5 @@ spec: kinds: - apiGroups: [""] kinds: ["Pod"] + parameters: + exemptImages: ["nonexistant/*"] diff --git a/library/pod-security-policy/allow-privilege-escalation/template.yaml b/library/pod-security-policy/allow-privilege-escalation/template.yaml index 5caaf14ca..66a01fdc1 100644 --- a/library/pod-security-policy/allow-privilege-escalation/template.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/template.yaml @@ -51,7 +51,7 @@ spec: !has(variables.params.exemptImages) ? [] : (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, // we can use the regex substitution because docker images do not allow "." as a valid character - variables.params.exemptImages.exists(exemption, container.image == exemption || (exemption.endsWith("*") && string(container.image).matches("^" + string(variables.params.exemptImages).replace("*", ".*") + "$"))) + variables.params.exemptImages.exists(exemption, container.image == exemption || (exemption.endsWith("*") && string(container.image).matches("^" + string(exemption).replace("*", ".*") + "$"))) ) - name: badContainers expression: | From 42d9910c9feebe40a8c407e470de603328cc3bab Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Fri, 10 May 2024 23:02:48 +0000 Subject: [PATCH 4/7] Use image path rather than container contents for exemptions Signed-off-by: Max Smythe --- .../allow-privilege-escalation/template.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/pod-security-policy/allow-privilege-escalation/template.yaml b/library/pod-security-policy/allow-privilege-escalation/template.yaml index 66a01fdc1..017f0cbc4 100644 --- a/library/pod-security-policy/allow-privilege-escalation/template.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/template.yaml @@ -52,11 +52,11 @@ spec: (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, // we can use the regex substitution because docker images do not allow "." as a valid character variables.params.exemptImages.exists(exemption, container.image == exemption || (exemption.endsWith("*") && string(container.image).matches("^" + string(exemption).replace("*", ".*") + "$"))) - ) + ).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, - !(container in variables.exemptImages) && ( + !(container.image in variables.exemptImages) && ( !has(container.securityContext) || !has(container.securityContext.allowPrivilegeEscalation) || container.securityContext.allowPrivilegeEscalation != false From fc226c5f16ca8801bfcc3bc808a072baf9732fa1 Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Tue, 14 May 2024 01:43:18 +0000 Subject: [PATCH 5/7] More explicity replicate glob-matching rego logic Signed-off-by: Max Smythe --- .../allow-privilege-escalation/template.yaml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/library/pod-security-policy/allow-privilege-escalation/template.yaml b/library/pod-security-policy/allow-privilege-escalation/template.yaml index 017f0cbc4..c3b74dcda 100644 --- a/library/pod-security-policy/allow-privilege-escalation/template.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/template.yaml @@ -46,13 +46,20 @@ spec: expression: 'has(object.spec.initContainers) ? object.spec.initContainers : []' - name: ephemeralContainers expression: 'has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : []' - - name: exemptImages + - name: exemptImagePrefixes expression: | !has(variables.params.exemptImages) ? [] : - (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, - // we can use the regex substitution because docker images do not allow "." as a valid character - variables.params.exemptImages.exists(exemption, container.image == exemption || (exemption.endsWith("*") && string(container.image).matches("^" + string(exemption).replace("*", ".*") + "$"))) - ).map(container, container.image) + variables.params.exemptImages.filter(image, image.endsWith("*")).map(image, string(image).replace("*", "")) + - name: exemptImageExplicit + expression: | + !has(variables.params.exemptImages) ? [] : + variables.params.exemptImages.filter(image, !image.endsWith("*")) + - name: exemptImages + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + container in variables.exemptImageExplicit || + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) + ).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, From 2735882016c9528790efa023fac060f432d461e1 Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Fri, 24 May 2024 05:54:35 +0000 Subject: [PATCH 6/7] Switch to build system, use anyObject Signed-off-by: Max Smythe --- .../allow-privilege-escalation/template.yaml | 118 +++++++++--------- .../constraint.tmpl | 20 +-- .../allow-privilege-escalation/src.cel | 33 +++++ 3 files changed, 106 insertions(+), 65 deletions(-) create mode 100644 src/pod-security-policy/allow-privilege-escalation/src.cel diff --git a/library/pod-security-policy/allow-privilege-escalation/template.yaml b/library/pod-security-policy/allow-privilege-escalation/template.yaml index c3b74dcda..8e8462777 100644 --- a/library/pod-security-policy/allow-privilege-escalation/template.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/template.yaml @@ -41,11 +41,11 @@ spec: source: variables: - name: containers - expression: 'has(object.spec.containers) ? object.spec.containers : []' + expression: 'has(variables.anyObject.spec.containers) ? variables.anyObject.spec.containers : []' - name: initContainers - expression: 'has(object.spec.initContainers) ? object.spec.initContainers : []' + expression: 'has(variables.anyObject.spec.initContainers) ? variables.anyObject.spec.initContainers : []' - name: ephemeralContainers - expression: 'has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : []' + expression: 'has(variables.anyObject.spec.ephemeralContainers) ? variables.anyObject.spec.ephemeralContainers : []' - name: exemptImagePrefixes expression: | !has(variables.params.exemptImages) ? [] : @@ -57,7 +57,7 @@ spec: - name: exemptImages expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, - container in variables.exemptImageExplicit || + container.image in variables.exemptImageExplicit || variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) ).map(container, container.image) - name: badContainers @@ -72,65 +72,67 @@ spec: validations: - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' messageExpression: '"Privilege escalation container is not allowed: " + variables.badContainers.map(c, c.image).join(", ")' - rego: | - package k8spspallowprivilegeescalationcontainer + - engine: Rego + source: + rego: | + package k8spspallowprivilegeescalationcontainer - import data.lib.exclude_update.is_update - import data.lib.exempt_container.is_exempt + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt - violation[{"msg": msg, "details": {}}] { - # spec.containers.securityContext.allowPrivilegeEscalation field is immutable. - not is_update(input.review) + violation[{"msg": msg, "details": {}}] { + # spec.containers.securityContext.allowPrivilegeEscalation field is immutable. + not is_update(input.review) - c := input_containers[_] - not is_exempt(c) - input_allow_privilege_escalation(c) - msg := sprintf("Privilege escalation container is not allowed: %v", [c.name]) - } + c := input_containers[_] + not is_exempt(c) + input_allow_privilege_escalation(c) + msg := sprintf("Privilege escalation container is not allowed: %v", [c.name]) + } - input_allow_privilege_escalation(c) { - not has_field(c, "securityContext") - } - input_allow_privilege_escalation(c) { - not c.securityContext.allowPrivilegeEscalation == false - } - input_containers[c] { - c := input.review.object.spec.containers[_] - } - input_containers[c] { - c := input.review.object.spec.initContainers[_] - } - input_containers[c] { - c := input.review.object.spec.ephemeralContainers[_] - } - # has_field returns whether an object has a field - has_field(object, field) = true { - object[field] - } - libs: - - | - package lib.exclude_update + input_allow_privilege_escalation(c) { + not has_field(c, "securityContext") + } + input_allow_privilege_escalation(c) { + not c.securityContext.allowPrivilegeEscalation == false + } + input_containers[c] { + c := input.review.object.spec.containers[_] + } + input_containers[c] { + c := input.review.object.spec.initContainers[_] + } + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + } + # has_field returns whether an object has a field + has_field(object, field) = true { + object[field] + } + libs: + - | + package lib.exclude_update - is_update(review) { - review.operation == "UPDATE" - } - - | - package lib.exempt_container + is_update(review) { + review.operation == "UPDATE" + } + - | + package lib.exempt_container - is_exempt(container) { - exempt_images := object.get(object.get(input, "parameters", {}), "exemptImages", []) - img := container.image - exemption := exempt_images[_] - _matches_exemption(img, exemption) - } + is_exempt(container) { + exempt_images := object.get(object.get(input, "parameters", {}), "exemptImages", []) + img := container.image + exemption := exempt_images[_] + _matches_exemption(img, exemption) + } - _matches_exemption(img, exemption) { - not endswith(exemption, "*") - exemption == img - } + _matches_exemption(img, exemption) { + not endswith(exemption, "*") + exemption == img + } - _matches_exemption(img, exemption) { - endswith(exemption, "*") - prefix := trim_suffix(exemption, "*") - startswith(img, prefix) - } + _matches_exemption(img, exemption) { + endswith(exemption, "*") + prefix := trim_suffix(exemption, "*") + startswith(img, prefix) + } diff --git a/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl b/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl index e5f6f323f..f34ee597e 100644 --- a/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl +++ b/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl @@ -36,10 +36,16 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | -{{ file.Read "src/pod-security-policy/allow-privilege-escalation/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }} - libs: - - | -{{ file.Read "src/pod-security-policy/allow-privilege-escalation/lib_exclude_update.rego" | strings.Indent 10 | strings.TrimSuffix "\n" }} - - | -{{ file.Read "src/pod-security-policy/allow-privilege-escalation/lib_exempt_container.rego" | strings.Indent 10 | strings.TrimSuffix "\n" }} + code: + - engine: K8sNativeValidation + source: +{{ file.Read "src/pod-security-policy/allow-privilege-escalation/src.cel" | strings.Indent 10 | strings.TrimSuffix "\n" }} + - engine: Rego + source: + rego: | +{{ file.Read "src/pod-security-policy/allow-privilege-escalation/src.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} + libs: + - | +{{ file.Read "src/pod-security-policy/allow-privilege-escalation/lib_exclude_update.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} + - | +{{ file.Read "src/pod-security-policy/allow-privilege-escalation/lib_exempt_container.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} diff --git a/src/pod-security-policy/allow-privilege-escalation/src.cel b/src/pod-security-policy/allow-privilege-escalation/src.cel new file mode 100644 index 000000000..f49fe66fc --- /dev/null +++ b/src/pod-security-policy/allow-privilege-escalation/src.cel @@ -0,0 +1,33 @@ +variables: +- name: containers + expression: 'has(variables.anyObject.spec.containers) ? variables.anyObject.spec.containers : []' +- name: initContainers + expression: 'has(variables.anyObject.spec.initContainers) ? variables.anyObject.spec.initContainers : []' +- name: ephemeralContainers + expression: 'has(variables.anyObject.spec.ephemeralContainers) ? variables.anyObject.spec.ephemeralContainers : []' +- name: exemptImagePrefixes + expression: | + !has(variables.params.exemptImages) ? [] : + variables.params.exemptImages.filter(image, image.endsWith("*")).map(image, string(image).replace("*", "")) +- name: exemptImageExplicit + expression: | + !has(variables.params.exemptImages) ? [] : + variables.params.exemptImages.filter(image, !image.endsWith("*")) +- name: exemptImages + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + container.image in variables.exemptImageExplicit || + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) + ).map(container, container.image) +- name: badContainers + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + !(container.image in variables.exemptImages) && ( + !has(container.securityContext) || + !has(container.securityContext.allowPrivilegeEscalation) || + container.securityContext.allowPrivilegeEscalation != false + ) + ) +validations: +- expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + messageExpression: '"Privilege escalation container is not allowed: " + variables.badContainers.map(c, c.image).join(", ")' \ No newline at end of file From a5a7cc2e36a8ee185a4a956b3258211433116815 Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Wed, 29 May 2024 23:41:55 +0000 Subject: [PATCH 7/7] Bump minor version Signed-off-by: Max Smythe --- .../1.1.0/artifacthub-pkg.yml | 22 +++ .../1.1.0/kustomization.yaml | 2 + .../constraint.yaml | 11 ++ .../disallowed_ephemeral.yaml | 12 ++ .../example_allowed.yaml | 12 ++ .../example_disallowed.yaml | 12 ++ .../update.yaml | 17 ++ .../1.1.0/suite.yaml | 25 +++ .../1.1.0/template.yaml | 138 +++++++++++++++ .../allow-privilege-escalation/template.yaml | 2 +- .../constraint.tmpl | 2 +- .../validation/allow-privilege-escalation.md | 166 +++++++++++------- 12 files changed, 356 insertions(+), 65 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/update.yaml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/template.yaml diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/artifacthub-pkg.yml new file mode 100644 index 000000000..ede58c1e2 --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.0 +name: k8spspallowprivilegeescalationcontainer +displayName: Allow Privilege Escalation in Container +createdAt: "2024-05-29T23:41:40Z" +description: Controls restricting escalation to root privileges. Corresponds to the `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation +digest: b88d5016b878fe37a461123069589385e3d082311bcaa93f9c73cedb0201cbbd +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/allow-privilege-escalation +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Allow Privilege Escalation in Container + Controls restricting escalation to root privileges. Corresponds to the `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/kustomization.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/kustomization.yaml new file mode 100644 index 000000000..7d70d11b7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/constraint.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/constraint.yaml new file mode 100644 index 000000000..df3f0d823 --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPAllowPrivilegeEscalationContainer +metadata: + name: psp-allow-privilege-escalation-container +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: ["nonexistant/*"] diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/disallowed_ephemeral.yaml new file mode 100644 index 000000000..5992f96b3 --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/disallowed_ephemeral.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privilege-escalation-disallowed + labels: + app: nginx-privilege-escalation +spec: + ephemeralContainers: + - name: nginx + image: nginx + securityContext: + allowPrivilegeEscalation: true diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed.yaml new file mode 100644 index 000000000..26c8dd879 --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privilege-escalation-allowed + labels: + app: nginx-privilege-escalation +spec: + containers: + - name: nginx + image: nginx + securityContext: + allowPrivilegeEscalation: false diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_disallowed.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_disallowed.yaml new file mode 100644 index 000000000..d3648d2f2 --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_disallowed.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privilege-escalation-disallowed + labels: + app: nginx-privilege-escalation +spec: + containers: + - name: nginx + image: nginx + securityContext: + allowPrivilegeEscalation: true diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/update.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/update.yaml new file mode 100644 index 000000000..a79d40a1b --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/update.yaml @@ -0,0 +1,17 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: nginx-privilege-escalation-disallowed + labels: + app: nginx-privilege-escalation + spec: + containers: + - name: nginx + image: nginx + securityContext: + allowPrivilegeEscalation: true diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/suite.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/suite.yaml new file mode 100644 index 000000000..cd7531cfa --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/suite.yaml @@ -0,0 +1,25 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: allow-privilege-escalation +tests: + - name: allow-privilege-escalation + template: template.yaml + constraint: samples/psp-allow-privilege-escalation-container/constraint.yaml + cases: + - name: example-allowed + object: samples/psp-allow-privilege-escalation-container/example_allowed.yaml + assertions: + - violations: no + - name: example-disallowed + object: samples/psp-allow-privilege-escalation-container/example_disallowed.yaml + assertions: + - violations: yes + - name: disallowed-ephemeral + object: samples/psp-allow-privilege-escalation-container/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-allow-privilege-escalation-container/update.yaml + assertions: + - violations: no diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/template.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/template.yaml new file mode 100644 index 000000000..8ad2cf7aa --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/template.yaml @@ -0,0 +1,138 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spspallowprivilegeescalationcontainer + annotations: + metadata.gatekeeper.sh/title: "Allow Privilege Escalation in Container" + metadata.gatekeeper.sh/version: 1.1.0 + description: >- + Controls restricting escalation to root privileges. Corresponds to the + `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation +spec: + crd: + spec: + names: + kind: K8sPSPAllowPrivilegeEscalationContainer + validation: + openAPIV3Schema: + type: object + description: >- + Controls restricting escalation to root privileges. Corresponds to the + `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation + properties: + exemptImages: + description: >- + Any container that uses an image that matches an entry in this list will be excluded + from enforcement. Prefix-matching can be signified with `*`. For example: `my-image-*`. + + It is recommended that users use the fully-qualified Docker image name (e.g. start with a domain name) + in order to avoid unexpectedly exempting images from an untrusted repository. + type: array + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + code: + - engine: K8sNativeValidation + source: + variables: + - name: containers + expression: 'has(variables.anyObject.spec.containers) ? variables.anyObject.spec.containers : []' + - name: initContainers + expression: 'has(variables.anyObject.spec.initContainers) ? variables.anyObject.spec.initContainers : []' + - name: ephemeralContainers + expression: 'has(variables.anyObject.spec.ephemeralContainers) ? variables.anyObject.spec.ephemeralContainers : []' + - name: exemptImagePrefixes + expression: | + !has(variables.params.exemptImages) ? [] : + variables.params.exemptImages.filter(image, image.endsWith("*")).map(image, string(image).replace("*", "")) + - name: exemptImageExplicit + expression: | + !has(variables.params.exemptImages) ? [] : + variables.params.exemptImages.filter(image, !image.endsWith("*")) + - name: exemptImages + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + container.image in variables.exemptImageExplicit || + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) + ).map(container, container.image) + - name: badContainers + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + !(container.image in variables.exemptImages) && ( + !has(container.securityContext) || + !has(container.securityContext.allowPrivilegeEscalation) || + container.securityContext.allowPrivilegeEscalation != false + ) + ) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + messageExpression: '"Privilege escalation container is not allowed: " + variables.badContainers.map(c, c.image).join(", ")' + - engine: Rego + source: + rego: | + package k8spspallowprivilegeescalationcontainer + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.containers.securityContext.allowPrivilegeEscalation field is immutable. + not is_update(input.review) + + c := input_containers[_] + not is_exempt(c) + input_allow_privilege_escalation(c) + msg := sprintf("Privilege escalation container is not allowed: %v", [c.name]) + } + + input_allow_privilege_escalation(c) { + not has_field(c, "securityContext") + } + input_allow_privilege_escalation(c) { + not c.securityContext.allowPrivilegeEscalation == false + } + input_containers[c] { + c := input.review.object.spec.containers[_] + } + input_containers[c] { + c := input.review.object.spec.initContainers[_] + } + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + } + # has_field returns whether an object has a field + has_field(object, field) = true { + object[field] + } + libs: + - | + package lib.exclude_update + + is_update(review) { + review.operation == "UPDATE" + } + - | + package lib.exempt_container + + is_exempt(container) { + exempt_images := object.get(object.get(input, "parameters", {}), "exemptImages", []) + img := container.image + exemption := exempt_images[_] + _matches_exemption(img, exemption) + } + + _matches_exemption(img, exemption) { + not endswith(exemption, "*") + exemption == img + } + + _matches_exemption(img, exemption) { + endswith(exemption, "*") + prefix := trim_suffix(exemption, "*") + startswith(img, prefix) + } diff --git a/library/pod-security-policy/allow-privilege-escalation/template.yaml b/library/pod-security-policy/allow-privilege-escalation/template.yaml index 8e8462777..8ad2cf7aa 100644 --- a/library/pod-security-policy/allow-privilege-escalation/template.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spspallowprivilegeescalationcontainer annotations: metadata.gatekeeper.sh/title: "Allow Privilege Escalation in Container" - metadata.gatekeeper.sh/version: 1.0.1 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls restricting escalation to root privileges. Corresponds to the `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more diff --git a/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl b/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl index f34ee597e..393bf2884 100644 --- a/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl +++ b/src/pod-security-policy/allow-privilege-escalation/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spspallowprivilegeescalationcontainer annotations: metadata.gatekeeper.sh/title: "Allow Privilege Escalation in Container" - metadata.gatekeeper.sh/version: 1.0.1 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls restricting escalation to root privileges. Corresponds to the `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more diff --git a/website/docs/validation/allow-privilege-escalation.md b/website/docs/validation/allow-privilege-escalation.md index fbb56a6b2..04bf12f69 100644 --- a/website/docs/validation/allow-privilege-escalation.md +++ b/website/docs/validation/allow-privilege-escalation.md @@ -16,7 +16,7 @@ metadata: name: k8spspallowprivilegeescalationcontainer annotations: metadata.gatekeeper.sh/title: "Allow Privilege Escalation in Container" - metadata.gatekeeper.sh/version: 1.0.1 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls restricting escalation to root privileges. Corresponds to the `allowPrivilegeEscalation` field in a PodSecurityPolicy. For more @@ -48,68 +48,106 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | - package k8spspallowprivilegeescalationcontainer - - import data.lib.exclude_update.is_update - import data.lib.exempt_container.is_exempt - - violation[{"msg": msg, "details": {}}] { - # spec.containers.securityContext.allowPrivilegeEscalation field is immutable. - not is_update(input.review) - - c := input_containers[_] - not is_exempt(c) - input_allow_privilege_escalation(c) - msg := sprintf("Privilege escalation container is not allowed: %v", [c.name]) - } - - input_allow_privilege_escalation(c) { - not has_field(c, "securityContext") - } - input_allow_privilege_escalation(c) { - not c.securityContext.allowPrivilegeEscalation == false - } - input_containers[c] { - c := input.review.object.spec.containers[_] - } - input_containers[c] { - c := input.review.object.spec.initContainers[_] - } - input_containers[c] { - c := input.review.object.spec.ephemeralContainers[_] - } - # has_field returns whether an object has a field - has_field(object, field) = true { - object[field] - } - libs: - - | - package lib.exclude_update - - is_update(review) { - review.operation == "UPDATE" - } - - | - package lib.exempt_container - - is_exempt(container) { - exempt_images := object.get(object.get(input, "parameters", {}), "exemptImages", []) - img := container.image - exemption := exempt_images[_] - _matches_exemption(img, exemption) - } - - _matches_exemption(img, exemption) { - not endswith(exemption, "*") - exemption == img - } - - _matches_exemption(img, exemption) { - endswith(exemption, "*") - prefix := trim_suffix(exemption, "*") - startswith(img, prefix) - } + code: + - engine: K8sNativeValidation + source: + variables: + - name: containers + expression: 'has(variables.anyObject.spec.containers) ? variables.anyObject.spec.containers : []' + - name: initContainers + expression: 'has(variables.anyObject.spec.initContainers) ? variables.anyObject.spec.initContainers : []' + - name: ephemeralContainers + expression: 'has(variables.anyObject.spec.ephemeralContainers) ? variables.anyObject.spec.ephemeralContainers : []' + - name: exemptImagePrefixes + expression: | + !has(variables.params.exemptImages) ? [] : + variables.params.exemptImages.filter(image, image.endsWith("*")).map(image, string(image).replace("*", "")) + - name: exemptImageExplicit + expression: | + !has(variables.params.exemptImages) ? [] : + variables.params.exemptImages.filter(image, !image.endsWith("*")) + - name: exemptImages + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + container.image in variables.exemptImageExplicit || + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) + ).map(container, container.image) + - name: badContainers + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + !(container.image in variables.exemptImages) && ( + !has(container.securityContext) || + !has(container.securityContext.allowPrivilegeEscalation) || + container.securityContext.allowPrivilegeEscalation != false + ) + ) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + messageExpression: '"Privilege escalation container is not allowed: " + variables.badContainers.map(c, c.image).join(", ")' + - engine: Rego + source: + rego: | + package k8spspallowprivilegeescalationcontainer + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.containers.securityContext.allowPrivilegeEscalation field is immutable. + not is_update(input.review) + + c := input_containers[_] + not is_exempt(c) + input_allow_privilege_escalation(c) + msg := sprintf("Privilege escalation container is not allowed: %v", [c.name]) + } + + input_allow_privilege_escalation(c) { + not has_field(c, "securityContext") + } + input_allow_privilege_escalation(c) { + not c.securityContext.allowPrivilegeEscalation == false + } + input_containers[c] { + c := input.review.object.spec.containers[_] + } + input_containers[c] { + c := input.review.object.spec.initContainers[_] + } + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + } + # has_field returns whether an object has a field + has_field(object, field) = true { + object[field] + } + libs: + - | + package lib.exclude_update + + is_update(review) { + review.operation == "UPDATE" + } + - | + package lib.exempt_container + + is_exempt(container) { + exempt_images := object.get(object.get(input, "parameters", {}), "exemptImages", []) + img := container.image + exemption := exempt_images[_] + _matches_exemption(img, exemption) + } + + _matches_exemption(img, exemption) { + not endswith(exemption, "*") + exemption == img + } + + _matches_exemption(img, exemption) { + endswith(exemption, "*") + prefix := trim_suffix(exemption, "*") + startswith(img, prefix) + } ``` @@ -134,6 +172,8 @@ spec: kinds: - apiGroups: [""] kinds: ["Pod"] + parameters: + exemptImages: ["nonexistant/*"] ```