From 0771e5f0daf9fde6cac533ddd9cfab398cf5ece7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:39:19 -0700 Subject: [PATCH 01/20] chore: bump the all group with 3 updates (#571) Bumps the all group with 3 updates: [step-security/harden-runner](https://github.com/step-security/harden-runner), [github/codeql-action](https://github.com/github/codeql-action) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `step-security/harden-runner` from 2.9.0 to 2.9.1 - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/0d381219ddf674d61a7572ddd19d7941e271515c...5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde) Updates `github/codeql-action` from 3.25.15 to 3.26.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/afb54ba388a7dca6ecae48f608c4ff05ff4cc77a...eb055d739abdc2e8de2e5f4ba1a8b246daa779aa) Updates `actions/upload-artifact` from 4.3.5 to 4.3.6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/89ef406dd8d7e03cfd12d9e0a4a378f454709029...834a144ee995460fba8ed112a2fc961b36a5ec5a) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 8 ++++---- .github/workflows/dependency-review.yml | 2 +- .github/workflows/scorecards.yml | 6 +++--- .github/workflows/website.yaml | 2 +- .github/workflows/workflow.yaml | 14 +++++++------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6a4fa781e0..6c81618fef 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,7 +41,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/autobuild@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index f072eabac8..bf2dcfbae9 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 8bfd53ed5b..bd2f7dcdc0 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -63,7 +63,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 + uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 with: name: SARIF file path: results.sarif @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/upload-sarif@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 with: sarif_file: results.sarif diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 25dcf8359f..45e51bc657 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -25,7 +25,7 @@ jobs: working-directory: website steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index a5212e1271..1af1df56fd 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -49,7 +49,7 @@ jobs: name: Unit test on ${{ matrix.os }} opa ${{ matrix.opa }} steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -71,7 +71,7 @@ jobs: steps: - name: Harden Runner if: ${{ !(matrix.gatekeeper == '3.15.1' && matrix.engine == 'cel') }} # remove this condition once 3.17 is out - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -99,7 +99,7 @@ jobs: kubectl logs -n gatekeeper-system -l control-plane=audit-controller --tail=-1 > logs-audit.json - name: Upload artifacts - uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 + uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 if: ${{ always() }} with: name: logs-int-test-${{ matrix.gatekeeper }}-${{ matrix.engine }} @@ -110,7 +110,7 @@ jobs: name: "Require a suite.yaml file alongside every template.yaml" steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -123,7 +123,7 @@ jobs: name: "Require a sync.yaml file and metadata.gatekeeper.sh/requires-sync-data annotation for every template.yaml using data.inventory" steps: - name: Harden Runner - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -141,7 +141,7 @@ jobs: steps: - name: Harden Runner if: ${{ !(matrix.gatekeeper == '3.15.1' && matrix.engine == 'cel') }} # remove this condition once 3.17 is out - uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit From 26ce562c129e281b4875b58fbd24422617729688 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:40:07 -0700 Subject: [PATCH 02/20] chore: bump golang from `86a3c48` to `2bd56f0` in /build/gomplate (#572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps golang from `86a3c48` to `2bd56f0`. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- build/gomplate/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gomplate/Dockerfile b/build/gomplate/Dockerfile index 6796b60c2b..21e0df9750 100644 --- a/build/gomplate/Dockerfile +++ b/build/gomplate/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22@sha256:86a3c48a61915a8c62c0e1d7594730399caa3feb73655dfe96c7bc17710e96cf +FROM golang:1.22@sha256:2bd56f00ff47baf33e64eae7996b65846c7cb5e0a46e0a882ef179fd89654afa ARG GOMPLATE_VERSION From c8fb36a233ab329a7205129c213aaa74a92301b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 12:08:20 -0700 Subject: [PATCH 03/20] chore: bump github/codeql-action from 3.26.0 to 3.26.2 in the all group (#577) Bumps the all group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.26.0 to 3.26.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/eb055d739abdc2e8de2e5f4ba1a8b246daa779aa...429e1977040da7a23b6822b13c129cd1ba93dbb2) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6c81618fef..7cca88aba0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 + uses: github/codeql-action/init@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 + uses: github/codeql-action/autobuild@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 + uses: github/codeql-action/analyze@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index bd2f7dcdc0..1765bec761 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 + uses: github/codeql-action/upload-sarif@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 with: sarif_file: results.sarif From ee2e26edb65000733ec00cb8643c3888a261d68a Mon Sep 17 00:00:00 2001 From: Andrew Peabody Date: Wed, 21 Aug 2024 13:21:31 -0700 Subject: [PATCH 04/20] fix(k8sdisallowanonymous): correct no allowed roles (#578) Signed-off-by: Andrew Peabody --- .../1.0.1/artifacthub-pkg.yml | 22 +++++++++ .../1.0.1/kustomization.yaml | 2 + .../no-anonymous-bindings/constraint.yaml | 14 ++++++ .../example_allowed.yaml | 15 ++++++ .../example_disallowed.yaml | 15 ++++++ .../disallowanonymous/1.0.1/suite.yaml | 17 +++++++ .../disallowanonymous/1.0.1/template.yaml | 48 +++++++++++++++++++ .../general/disallowanonymous/template.yaml | 4 +- src/general/disallowanonymous/constraint.tmpl | 2 +- src/general/disallowanonymous/src.rego | 2 +- src/general/disallowanonymous/src_test.rego | 24 ++++++++++ website/docs/validation/disallowanonymous.md | 4 +- 12 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 artifacthub/library/general/disallowanonymous/1.0.1/artifacthub-pkg.yml create mode 100644 artifacthub/library/general/disallowanonymous/1.0.1/kustomization.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/constraint.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_allowed.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_disallowed.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.0.1/suite.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.0.1/template.yaml diff --git a/artifacthub/library/general/disallowanonymous/1.0.1/artifacthub-pkg.yml b/artifacthub/library/general/disallowanonymous/1.0.1/artifacthub-pkg.yml new file mode 100644 index 0000000000..30f0ccfe40 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.0.1/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.0.1 +name: k8sdisallowanonymous +displayName: Disallow Anonymous Access +createdAt: "2024-08-19T17:43:37Z" +description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. +digest: eef7a39c0a9dfa0ff63d357b5e0ce881b1acc388c1b991a6cf4ab3745feb3e4a +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/disallowanonymous +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Disallow Anonymous Access + Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/general/disallowanonymous/1.0.1/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/general/disallowanonymous/1.0.1/kustomization.yaml b/artifacthub/library/general/disallowanonymous/1.0.1/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.0.1/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/constraint.yaml b/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/constraint.yaml new file mode 100644 index 0000000000..1d14ae1333 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/constraint.yaml @@ -0,0 +1,14 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sDisallowAnonymous +metadata: + name: no-anonymous +spec: + match: + kinds: + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["ClusterRoleBinding"] + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["RoleBinding"] + parameters: + allowedRoles: + - cluster-role-1 diff --git a/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_allowed.yaml b/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_allowed.yaml new file mode 100644 index 0000000000..f8889d941d --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_allowed.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-1 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:unauthenticated diff --git a/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_disallowed.yaml b/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_disallowed.yaml new file mode 100644 index 0000000000..1f34ffd642 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.0.1/samples/no-anonymous-bindings/example_disallowed.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-2 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:unauthenticated diff --git a/artifacthub/library/general/disallowanonymous/1.0.1/suite.yaml b/artifacthub/library/general/disallowanonymous/1.0.1/suite.yaml new file mode 100644 index 0000000000..46daa7f2fb --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.0.1/suite.yaml @@ -0,0 +1,17 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: disallowanonymous +tests: +- name: disallow-anonymous + template: template.yaml + constraint: samples/no-anonymous-bindings/constraint.yaml + cases: + - name: example-allowed + object: samples/no-anonymous-bindings/example_allowed.yaml + assertions: + - violations: no + - name: example-disallowed + object: samples/no-anonymous-bindings/example_disallowed.yaml + assertions: + - violations: yes \ No newline at end of file diff --git a/artifacthub/library/general/disallowanonymous/1.0.1/template.yaml b/artifacthub/library/general/disallowanonymous/1.0.1/template.yaml new file mode 100644 index 0000000000..1f090f3521 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.0.1/template.yaml @@ -0,0 +1,48 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8sdisallowanonymous + annotations: + metadata.gatekeeper.sh/title: "Disallow Anonymous Access" + metadata.gatekeeper.sh/version: 1.0.1 + description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. +spec: + crd: + spec: + names: + kind: K8sDisallowAnonymous + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + properties: + allowedRoles: + description: >- + The list of ClusterRoles and Roles that may be associated + with the `system:unauthenticated` group and `system:anonymous` + user. + type: array + items: + type: string + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sdisallowanonymous + + violation[{"msg": msg}] { + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) + review(input.review.object.subjects[_]) + msg := sprintf("Unauthenticated user reference is not allowed in %v %v ", [input.review.object.kind, input.review.object.metadata.name]) + } + + is_allowed(role, allowedRoles) { + role.name == allowedRoles[_] + } + + review(subject) = true { + subject.name == "system:unauthenticated" + } + + review(subject) = true { + subject.name == "system:anonymous" + } diff --git a/library/general/disallowanonymous/template.yaml b/library/general/disallowanonymous/template.yaml index c283aa769d..1f090f3521 100644 --- a/library/general/disallowanonymous/template.yaml +++ b/library/general/disallowanonymous/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8sdisallowanonymous annotations: metadata.gatekeeper.sh/title: "Disallow Anonymous Access" - metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/version: 1.0.1 description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. spec: crd: @@ -30,7 +30,7 @@ spec: package k8sdisallowanonymous violation[{"msg": msg}] { - not is_allowed(input.review.object.roleRef, input.parameters.allowedRoles) + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) review(input.review.object.subjects[_]) msg := sprintf("Unauthenticated user reference is not allowed in %v %v ", [input.review.object.kind, input.review.object.metadata.name]) } diff --git a/src/general/disallowanonymous/constraint.tmpl b/src/general/disallowanonymous/constraint.tmpl index d52da2e46b..ff7caf000e 100644 --- a/src/general/disallowanonymous/constraint.tmpl +++ b/src/general/disallowanonymous/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8sdisallowanonymous annotations: metadata.gatekeeper.sh/title: "Disallow Anonymous Access" - metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/version: 1.0.1 description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. spec: crd: diff --git a/src/general/disallowanonymous/src.rego b/src/general/disallowanonymous/src.rego index edb2867375..fee795a3dc 100644 --- a/src/general/disallowanonymous/src.rego +++ b/src/general/disallowanonymous/src.rego @@ -1,7 +1,7 @@ package k8sdisallowanonymous violation[{"msg": msg}] { - not is_allowed(input.review.object.roleRef, input.parameters.allowedRoles) + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) review(input.review.object.subjects[_]) msg := sprintf("Unauthenticated user reference is not allowed in %v %v ", [input.review.object.kind, input.review.object.metadata.name]) } diff --git a/src/general/disallowanonymous/src_test.rego b/src/general/disallowanonymous/src_test.rego index 6a6c5759f3..ff951c9b35 100644 --- a/src/general/disallowanonymous/src_test.rego +++ b/src/general/disallowanonymous/src_test.rego @@ -24,6 +24,12 @@ test_anonymous_user_clusterrolebinding { count(results) == 1 } +test_anonymous_user_clusterrolebinding_no_param { + inp := {"review": clusterrolebinding([{"name": "system:anonymous", "kind": "User"}], "role-2")} + results := violation with input as inp + count(results) == 1 +} + test_allowed_role_anonymous_user_clusterrolebinding { inp := {"review": clusterrolebinding([{"name": "system:anonymous", "kind": "User"}], "role-2"), "parameters": {"allowedRoles": ["role-2"]}} results := violation with input as inp @@ -60,6 +66,12 @@ test_unauthenticated_group_clusterrolebinding { count(results) == 1 } +test_unauthenticated_group_clusterrolebinding_no_param { + inp := {"review": clusterrolebinding([{"name": "system:unauthenticated", "kind": "Group"}], "role-2")} + results := violation with input as inp + count(results) == 1 +} + test_allowed_role_unauthenticated_group_clusterrolebinding { inp := {"review": clusterrolebinding([{"name": "system:unauthenticated", "kind": "Group"}], "role-2"), "parameters": {"allowedRoles": ["role-2"]}} results := violation with input as inp @@ -132,6 +144,12 @@ test_anonymous_user_rolebinding { count(results) == 1 } +test_anonymous_user_rolebinding_no_param { + inp := {"review": rolebinding([{"name": "system:anonymous", "kind": "User"}], "role-2")} + results := violation with input as inp + count(results) == 1 +} + test_allowed_role_anonymous_user_rolebinding { inp := {"review": rolebinding([{"name": "system:anonymous", "kind": "User"}], "role-2"), "parameters": {"allowedRoles": ["role-2"]}} results := violation with input as inp @@ -162,6 +180,12 @@ test_allowed_multiple_role_multiple_subjects_anonymous_user_rolebinding { count(results) == 0 } +test_unauthenticated_group_rolebinding_no_param { + inp := {"review": rolebinding([{"name": "system:unauthenticated", "kind": "Group"}], "role-2")} + results := violation with input as inp + count(results) == 1 +} + test_unauthenticated_group_rolebinding { inp := {"review": rolebinding([{"name": "system:unauthenticated", "kind": "Group"}], "role-2"), "parameters": {"allowedRoles": ["role-1"]}} results := violation with input as inp diff --git a/website/docs/validation/disallowanonymous.md b/website/docs/validation/disallowanonymous.md index 7652f68ea1..12526a9a0d 100644 --- a/website/docs/validation/disallowanonymous.md +++ b/website/docs/validation/disallowanonymous.md @@ -16,7 +16,7 @@ metadata: name: k8sdisallowanonymous annotations: metadata.gatekeeper.sh/title: "Disallow Anonymous Access" - metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/version: 1.0.1 description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. spec: crd: @@ -42,7 +42,7 @@ spec: package k8sdisallowanonymous violation[{"msg": msg}] { - not is_allowed(input.review.object.roleRef, input.parameters.allowedRoles) + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) review(input.review.object.subjects[_]) msg := sprintf("Unauthenticated user reference is not allowed in %v %v ", [input.review.object.kind, input.review.object.metadata.name]) } From 9ca2300403aeed475c45f2ac775c59f464563769 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Mon, 26 Aug 2024 12:54:45 -0700 Subject: [PATCH 05/20] fix(k8spspallowprivilegeescalationcontainer): typo fix (#581) s/existant/existent Signed-off-by: juliankatz --- .../psp-allow-privilege-escalation-container/constraint.yaml | 2 +- .../psp-allow-privilege-escalation-container/constraint.yaml | 2 +- website/docs/validation/allow-privilege-escalation.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 index df3f0d823c..1d4f031c40 100644 --- 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 @@ -8,4 +8,4 @@ spec: - apiGroups: [""] kinds: ["Pod"] parameters: - exemptImages: ["nonexistant/*"] + exemptImages: ["nonexistent/*"] 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 df3f0d823c..1d4f031c40 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 @@ -8,4 +8,4 @@ spec: - apiGroups: [""] kinds: ["Pod"] parameters: - exemptImages: ["nonexistant/*"] + exemptImages: ["nonexistent/*"] diff --git a/website/docs/validation/allow-privilege-escalation.md b/website/docs/validation/allow-privilege-escalation.md index 04bf12f694..e36f366889 100644 --- a/website/docs/validation/allow-privilege-escalation.md +++ b/website/docs/validation/allow-privilege-escalation.md @@ -173,7 +173,7 @@ spec: - apiGroups: [""] kinds: ["Pod"] parameters: - exemptImages: ["nonexistant/*"] + exemptImages: ["nonexistent/*"] ``` From 1002cf8e4293d5184bd8b140ca42066a9fd70cf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 13:14:24 -0700 Subject: [PATCH 06/20] chore: bump github/codeql-action from 3.26.2 to 3.26.5 in the all group (#580) Bumps the all group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.26.2 to 3.26.5 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/429e1977040da7a23b6822b13c129cd1ba93dbb2...2c779ab0d087cd7fe7b826087247c2c81f27bfa6) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7cca88aba0..b0267abcec 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 + uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 + uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 + uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 1765bec761..fee0d76379 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 + uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 with: sarif_file: results.sarif From caf342f29e0786ecfc973f7d80905316342010b8 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Mon, 26 Aug 2024 15:16:59 -0700 Subject: [PATCH 07/20] fix(k8srequiredlabels): CEL broke when allowedRegex was empty (#583) The CEL implementation of this policy always expected the `allowedRegex` value to be set for a given `key` value in the contraint parameters. Existing users of the template could (via rego) require only that a given label key exist, but assert nothing about the value of that label. This change makes the CEL implementation uphold that same contract. Signed-off-by: juliankatz --- .../requiredlabels/1.1.2/artifacthub-pkg.yml | 22 +++++ .../requiredlabels/1.1.2/kustomization.yaml | 2 + .../all-must-have-owner/constraint.yaml | 14 +++ .../all-must-have-owner/example_allowed.yaml | 6 ++ .../example_disallowed.yaml | 4 + .../example_disallowed_label_value.yaml | 6 ++ .../verify-label-key-only/constraint.yaml | 13 +++ .../example_allowed.yaml | 10 +++ .../example_disallowed.yaml | 10 +++ .../general/requiredlabels/1.1.2/suite.yaml | 33 +++++++ .../requiredlabels/1.1.2/template.yaml | 79 +++++++++++++++++ .../verify-label-key-only/constraint.yaml | 13 +++ .../example_allowed.yaml | 10 +++ .../example_disallowed.yaml | 10 +++ library/general/requiredlabels/suite.yaml | 12 +++ library/general/requiredlabels/template.yaml | 4 +- src/general/requiredlabels/constraint.tmpl | 2 +- src/general/requiredlabels/src.cel | 4 +- website/docs/validation/requiredlabels.md | 85 ++++++++++++++++++- 19 files changed, 332 insertions(+), 7 deletions(-) create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/artifacthub-pkg.yml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/kustomization.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/constraint.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_allowed.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed_label_value.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/constraint.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_allowed.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_disallowed.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/suite.yaml create mode 100644 artifacthub/library/general/requiredlabels/1.1.2/template.yaml create mode 100644 library/general/requiredlabels/samples/verify-label-key-only/constraint.yaml create mode 100644 library/general/requiredlabels/samples/verify-label-key-only/example_allowed.yaml create mode 100644 library/general/requiredlabels/samples/verify-label-key-only/example_disallowed.yaml diff --git a/artifacthub/library/general/requiredlabels/1.1.2/artifacthub-pkg.yml b/artifacthub/library/general/requiredlabels/1.1.2/artifacthub-pkg.yml new file mode 100644 index 0000000000..eb0cd9195b --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.2 +name: k8srequiredlabels +displayName: Required Labels +createdAt: "2024-08-26T21:23:58Z" +description: Requires resources to contain specified labels, with values matching provided regular expressions. +digest: 707994cb63de5a067bc9808caa5f66eb7831a4a9a19849c7eb2bb24b112e0726 +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/requiredlabels +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Required Labels + Requires resources to contain specified labels, with values matching provided regular expressions. +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/general/requiredlabels/1.1.2/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/general/requiredlabels/1.1.2/kustomization.yaml b/artifacthub/library/general/requiredlabels/1.1.2/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/constraint.yaml b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/constraint.yaml new file mode 100644 index 0000000000..806e9862ff --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/constraint.yaml @@ -0,0 +1,14 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: all-must-have-owner +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Namespace"] + parameters: + message: "All namespaces must have an `owner` label that points to your company username" + labels: + - key: owner + allowedRegex: "^[a-zA-Z]+.agilebank.demo$" diff --git a/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_allowed.yaml b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_allowed.yaml new file mode 100644 index 0000000000..e2d3b9c032 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_allowed.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: allowed-namespace + labels: + owner: user.agilebank.demo diff --git a/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed.yaml b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed.yaml new file mode 100644 index 0000000000..a7a53610a7 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: disallowed-namespace diff --git a/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed_label_value.yaml b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed_label_value.yaml new file mode 100644 index 0000000000..36ae771766 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/samples/all-must-have-owner/example_disallowed_label_value.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: disallowed-namespace + labels: + owner: user diff --git a/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/constraint.yaml b/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/constraint.yaml new file mode 100644 index 0000000000..ead40234a5 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: must-have-pizza +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + message: "All pods must have label of key `pizza` regardless of the label's value" + labels: + - key: pizza diff --git a/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_allowed.yaml b/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_allowed.yaml new file mode 100644 index 0000000000..3a904e3336 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_allowed.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: has-pizza + labels: + pizza: is-great +spec: + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_disallowed.yaml b/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_disallowed.yaml new file mode 100644 index 0000000000..fcf5b3e940 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/samples/verify-label-key-only/example_disallowed.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: does-not-have-pizza + labels: + taco: is-great +spec: + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/general/requiredlabels/1.1.2/suite.yaml b/artifacthub/library/general/requiredlabels/1.1.2/suite.yaml new file mode 100644 index 0000000000..9a0d2d7573 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/suite.yaml @@ -0,0 +1,33 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: requiredlabels +tests: +- name: must-have-owner + template: template.yaml + constraint: samples/all-must-have-owner/constraint.yaml + cases: + - name: example-allowed + object: samples/all-must-have-owner/example_allowed.yaml + assertions: + - violations: no + - name: example-disallowed + object: samples/all-must-have-owner/example_disallowed.yaml + assertions: + - violations: yes + - name: example-disallowed-label-value + object: samples/all-must-have-owner/example_disallowed_label_value.yaml + assertions: + - violations: yes +- name: must-have-key + template: template.yaml + constraint: samples/verify-label-key-only/constraint.yaml + cases: + - name: label-present + object: samples/verify-label-key-only/example_allowed.yaml + assertions: + - violations: no + - name: label-missing + object: samples/verify-label-key-only/example_disallowed.yaml + assertions: + - violations: yes diff --git a/artifacthub/library/general/requiredlabels/1.1.2/template.yaml b/artifacthub/library/general/requiredlabels/1.1.2/template.yaml new file mode 100644 index 0000000000..5404fadc72 --- /dev/null +++ b/artifacthub/library/general/requiredlabels/1.1.2/template.yaml @@ -0,0 +1,79 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8srequiredlabels + annotations: + metadata.gatekeeper.sh/title: "Required Labels" + metadata.gatekeeper.sh/version: 1.1.2 + description: >- + Requires resources to contain specified labels, with values matching + provided regular expressions. +spec: + crd: + spec: + names: + kind: K8sRequiredLabels + validation: + openAPIV3Schema: + type: object + properties: + message: + type: string + labels: + type: array + description: >- + A list of labels and values the object must specify. + items: + type: object + properties: + key: + type: string + description: >- + The required label. + allowedRegex: + type: string + description: >- + If specified, a regular expression the annotation's value + must match. The value must contain at least one match for + the regular expression. + targets: + - target: admission.k8s.gatekeeper.sh + code: + - engine: K8sNativeValidation + source: + validations: + - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels))' + messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")' + - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && (!has(entry.allowedRegex) || string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex)))))' + message: "regex mismatch" + - engine: Rego + source: + rego: | + package k8srequiredlabels + + get_message(parameters, _default) := _default { + not parameters.message + } + + get_message(parameters, _) := parameters.message + + violation[{"msg": msg, "details": {"missing_labels": missing}}] { + provided := {label | input.review.object.metadata.labels[label]} + required := {label | label := input.parameters.labels[_].key} + missing := required - provided + count(missing) > 0 + def_msg := sprintf("you must provide labels: %v", [missing]) + msg := get_message(input.parameters, def_msg) + } + + violation[{"msg": msg}] { + value := input.review.object.metadata.labels[key] + expected := input.parameters.labels[_] + expected.key == key + # do not match if allowedRegex is not defined, or is an empty string + expected.allowedRegex != "" + not regex.match(expected.allowedRegex, value) + def_msg := sprintf("Label <%v: %v> does not satisfy allowed regex: %v", [key, value, expected.allowedRegex]) + msg := get_message(input.parameters, def_msg) + } + diff --git a/library/general/requiredlabels/samples/verify-label-key-only/constraint.yaml b/library/general/requiredlabels/samples/verify-label-key-only/constraint.yaml new file mode 100644 index 0000000000..ead40234a5 --- /dev/null +++ b/library/general/requiredlabels/samples/verify-label-key-only/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: must-have-pizza +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + message: "All pods must have label of key `pizza` regardless of the label's value" + labels: + - key: pizza diff --git a/library/general/requiredlabels/samples/verify-label-key-only/example_allowed.yaml b/library/general/requiredlabels/samples/verify-label-key-only/example_allowed.yaml new file mode 100644 index 0000000000..3a904e3336 --- /dev/null +++ b/library/general/requiredlabels/samples/verify-label-key-only/example_allowed.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: has-pizza + labels: + pizza: is-great +spec: + containers: + - name: nginx + image: nginx diff --git a/library/general/requiredlabels/samples/verify-label-key-only/example_disallowed.yaml b/library/general/requiredlabels/samples/verify-label-key-only/example_disallowed.yaml new file mode 100644 index 0000000000..fcf5b3e940 --- /dev/null +++ b/library/general/requiredlabels/samples/verify-label-key-only/example_disallowed.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: does-not-have-pizza + labels: + taco: is-great +spec: + containers: + - name: nginx + image: nginx diff --git a/library/general/requiredlabels/suite.yaml b/library/general/requiredlabels/suite.yaml index 0995d8ea0e..9a0d2d7573 100644 --- a/library/general/requiredlabels/suite.yaml +++ b/library/general/requiredlabels/suite.yaml @@ -19,3 +19,15 @@ tests: object: samples/all-must-have-owner/example_disallowed_label_value.yaml assertions: - violations: yes +- name: must-have-key + template: template.yaml + constraint: samples/verify-label-key-only/constraint.yaml + cases: + - name: label-present + object: samples/verify-label-key-only/example_allowed.yaml + assertions: + - violations: no + - name: label-missing + object: samples/verify-label-key-only/example_disallowed.yaml + assertions: + - violations: yes diff --git a/library/general/requiredlabels/template.yaml b/library/general/requiredlabels/template.yaml index 7a47afe27d..5404fadc72 100644 --- a/library/general/requiredlabels/template.yaml +++ b/library/general/requiredlabels/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8srequiredlabels annotations: metadata.gatekeeper.sh/title: "Required Labels" - metadata.gatekeeper.sh/version: 1.1.1 + metadata.gatekeeper.sh/version: 1.1.2 description: >- Requires resources to contain specified labels, with values matching provided regular expressions. @@ -44,7 +44,7 @@ spec: validations: - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels))' messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")' - - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex))))' + - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && (!has(entry.allowedRegex) || string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex)))))' message: "regex mismatch" - engine: Rego source: diff --git a/src/general/requiredlabels/constraint.tmpl b/src/general/requiredlabels/constraint.tmpl index d3a57e4fbd..fd436f50b5 100644 --- a/src/general/requiredlabels/constraint.tmpl +++ b/src/general/requiredlabels/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8srequiredlabels annotations: metadata.gatekeeper.sh/title: "Required Labels" - metadata.gatekeeper.sh/version: 1.1.1 + metadata.gatekeeper.sh/version: 1.1.2 description: >- Requires resources to contain specified labels, with values matching provided regular expressions. diff --git a/src/general/requiredlabels/src.cel b/src/general/requiredlabels/src.cel index 2637d64b0b..41ae481ddf 100644 --- a/src/general/requiredlabels/src.cel +++ b/src/general/requiredlabels/src.cel @@ -1,5 +1,5 @@ validations: - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels))' messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")' -- expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex))))' - message: "regex mismatch" \ No newline at end of file +- expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && (!has(entry.allowedRegex) || string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex)))))' + message: "regex mismatch" diff --git a/website/docs/validation/requiredlabels.md b/website/docs/validation/requiredlabels.md index 3508ac5780..2bc76c52a5 100644 --- a/website/docs/validation/requiredlabels.md +++ b/website/docs/validation/requiredlabels.md @@ -16,7 +16,7 @@ metadata: name: k8srequiredlabels annotations: metadata.gatekeeper.sh/title: "Required Labels" - metadata.gatekeeper.sh/version: 1.1.1 + metadata.gatekeeper.sh/version: 1.1.2 description: >- Requires resources to contain specified labels, with values matching provided regular expressions. @@ -56,7 +56,7 @@ spec: validations: - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels))' messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")' - - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex))))' + - expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && (!has(entry.allowedRegex) || string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex)))))' message: "regex mismatch" - engine: Rego source: @@ -189,4 +189,85 @@ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper- +
+must-have-key + +
+constraint + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: must-have-pizza +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + message: "All pods must have label of key `pizza` regardless of the label's value" + labels: + - key: pizza + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredlabels/samples/verify-label-key-only/constraint.yaml +``` + +
+ +
+label-present + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: has-pizza + labels: + pizza: is-great +spec: + containers: + - name: nginx + image: nginx + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredlabels/samples/verify-label-key-only/example_allowed.yaml +``` + +
+
+label-missing + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: does-not-have-pizza + labels: + taco: is-great +spec: + containers: + - name: nginx + image: nginx + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredlabels/samples/verify-label-key-only/example_disallowed.yaml +``` + +
+ +
\ No newline at end of file From d975f10763f264bd6642185a3ef1d158917d3459 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Mon, 26 Aug 2024 16:05:35 -0700 Subject: [PATCH 08/20] fix(k8spsphostnetworkingports): null check container.ports (#582) Previously, the `badContainers` section of the K8sNative validation logic did not verify that the `ports` variable was set on a container spec in a pod. As this template's purpose is to verify that the specified ports fall within a given range, we would expect that _no ports_ is not a violation. Signed-off-by: juliankatz Co-authored-by: Andrew Peabody --- .../1.1.2/artifacthub-pkg.yml | 22 +++ .../1.1.2/kustomization.yaml | 2 + .../psp-host-network-ports/constraint.yaml | 13 ++ .../constraint_block_host_network.yaml | 13 ++ .../disallowed_ephemeral.yaml | 14 ++ .../example_allowed.yaml | 14 ++ .../example_allowed_no_ports.yaml | 11 ++ .../example_disallowed.yaml | 14 ++ .../psp-host-network-ports/update.yaml | 19 +++ .../host-network-ports/1.1.2/suite.yaml | 50 ++++++ .../host-network-ports/1.1.2/template.yaml | 159 ++++++++++++++++++ .../example_allowed_no_ports.yaml | 11 ++ .../host-network-ports/suite.yaml | 5 + .../host-network-ports/template.yaml | 4 +- .../host-network-ports/constraint.tmpl | 2 +- .../host-network-ports/src.cel | 4 +- website/docs/validation/host-network-ports.md | 29 +++- 17 files changed, 379 insertions(+), 7 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint_block_host_network.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed_no_ports.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/update.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.2/template.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports.yaml diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/artifacthub-pkg.yml new file mode 100644 index 0000000000..9900db87a2 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.2 +name: k8spsphostnetworkingports +displayName: Host Networking Ports +createdAt: "2024-08-26T20:25:17Z" +description: Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and `hostPorts` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +digest: 29aa57aacfb5f7fa92cd7cdc241250ed0ae1619d1706228f03f058e4efe7cabe +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/host-network-ports +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Host Networking Ports + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and `hostPorts` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/kustomization.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint.yaml new file mode 100644 index 0000000000..aba7c24e76 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network-ports +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: true + min: 80 + max: 9000 \ No newline at end of file diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint_block_host_network.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint_block_host_network.yaml new file mode 100644 index 0000000000..7ef87dbb39 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/constraint_block_host_network.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: false + exemptImages: + - "nginx" \ No newline at end of file diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..7a4fa31144 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/disallowed_ephemeral.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + ephemeralContainers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed.yaml new file mode 100644 index 0000000000..08b321fe58 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-allowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: false + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9000 + hostPort: 80 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed_no_ports.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed_no_ports.yaml new file mode 100644 index 0000000000..e009decf90 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_allowed_no_ports.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_disallowed.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_disallowed.yaml new file mode 100644 index 0000000000..9a496cd606 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/example_disallowed.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/update.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/update.yaml new file mode 100644 index 0000000000..231096430e --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/samples/psp-host-network-ports/update.yaml @@ -0,0 +1,19 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports + spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/suite.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/suite.yaml new file mode 100644 index 0000000000..b0c7f7816c --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/suite.yaml @@ -0,0 +1,50 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: host-network-ports +tests: +- name: use-of-host-networking-ports-blocked + template: template.yaml + constraint: samples/psp-host-network-ports/constraint.yaml + cases: + - name: example-disallowed + object: samples/psp-host-network-ports/example_disallowed.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/psp-host-network-ports/example_allowed.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-host-network-ports/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-host-network-ports/update.yaml + assertions: + - violations: no + - name: no-ports-specified + object: samples/psp-host-network-ports/example_allowed_no_ports.yaml + assertions: + - violations: no +- name: use-of-host-network-blocked + template: template.yaml + constraint: samples/psp-host-network-ports/constraint_block_host_network.yaml + cases: + - name: example-disallowed + object: samples/psp-host-network-ports/example_disallowed.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/psp-host-network-ports/example_allowed.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-host-network-ports/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-host-network-ports/update.yaml + assertions: + - violations: no + diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/template.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/template.yaml new file mode 100644 index 0000000000..218cb36e7d --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.2/template.yaml @@ -0,0 +1,159 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spsphostnetworkingports + annotations: + metadata.gatekeeper.sh/title: "Host Networking Ports" + metadata.gatekeeper.sh/version: 1.1.2 + description: >- + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific + ports must be specified. Corresponds to the `hostNetwork` and + `hostPorts` fields in a PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +spec: + crd: + spec: + names: + kind: K8sPSPHostNetworkingPorts + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific + ports must be specified. Corresponds to the `hostNetwork` and + `hostPorts` fields in a PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces + 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 + hostNetwork: + description: "Determines if the policy allows the use of HostNetwork in the pod spec." + type: boolean + min: + description: "The start of the allowed port range, inclusive." + type: integer + max: + description: "The end of the allowed port range, inclusive." + type: integer + 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))) + - name: badContainers + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + !(container.image in variables.exemptImages) && has(container.ports) && + ( + (container.ports.all(port, has(port.hostPort) && has(variables.params.min) && port.hostPort < variables.params.min)) || + (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) + ) + ) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' + - expression: | + (has(request.operation) && request.operation == "UPDATE") || + (!has(variables.params.hostNetwork) || !variables.params.hostNetwork ? (has(variables.anyObject.spec.hostNetwork) && !variables.anyObject.spec.hostNetwork) : true) + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' + - engine: Rego + source: + rego: | + package k8spsphostnetworkingports + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.hostNetwork field is immutable. + not is_update(input.review) + + input_share_hostnetwork(input.review.object) + msg := sprintf("The specified hostNetwork and hostPort are not allowed, pod: %v. Allowed values: %v", [input.review.object.metadata.name, input.parameters]) + } + + input_share_hostnetwork(o) { + not input.parameters.hostNetwork + o.spec.hostNetwork + } + + input_share_hostnetwork(_) { + hostPort := input_containers[_].ports[_].hostPort + hostPort < input.parameters.min + } + + input_share_hostnetwork(_) { + hostPort := input_containers[_].ports[_].hostPort + hostPort > input.parameters.max + } + + input_containers[c] { + c := input.review.object.spec.containers[_] + not is_exempt(c) + } + + input_containers[c] { + c := input.review.object.spec.initContainers[_] + not is_exempt(c) + } + + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + not is_exempt(c) + } + 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/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports.yaml b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports.yaml new file mode 100644 index 0000000000..e009decf90 --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx diff --git a/library/pod-security-policy/host-network-ports/suite.yaml b/library/pod-security-policy/host-network-ports/suite.yaml index bb7e60a568..b0c7f7816c 100644 --- a/library/pod-security-policy/host-network-ports/suite.yaml +++ b/library/pod-security-policy/host-network-ports/suite.yaml @@ -23,6 +23,10 @@ tests: object: samples/psp-host-network-ports/update.yaml assertions: - violations: no + - name: no-ports-specified + object: samples/psp-host-network-ports/example_allowed_no_ports.yaml + assertions: + - violations: no - name: use-of-host-network-blocked template: template.yaml constraint: samples/psp-host-network-ports/constraint_block_host_network.yaml @@ -43,3 +47,4 @@ tests: object: samples/psp-host-network-ports/update.yaml assertions: - violations: no + diff --git a/library/pod-security-policy/host-network-ports/template.yaml b/library/pod-security-policy/host-network-ports/template.yaml index c04d03938c..218cb36e7d 100644 --- a/library/pod-security-policy/host-network-ports/template.yaml +++ b/library/pod-security-policy/host-network-ports/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.1 + metadata.gatekeeper.sh/version: 1.1.2 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and @@ -72,7 +72,7 @@ spec: - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, - !(container.image in variables.exemptImages) && + !(container.image in variables.exemptImages) && has(container.ports) && ( (container.ports.all(port, has(port.hostPort) && has(variables.params.min) && port.hostPort < variables.params.min)) || (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) diff --git a/src/pod-security-policy/host-network-ports/constraint.tmpl b/src/pod-security-policy/host-network-ports/constraint.tmpl index 0ab4d5e7a2..9f6e90d281 100644 --- a/src/pod-security-policy/host-network-ports/constraint.tmpl +++ b/src/pod-security-policy/host-network-ports/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.1 + metadata.gatekeeper.sh/version: 1.1.2 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and diff --git a/src/pod-security-policy/host-network-ports/src.cel b/src/pod-security-policy/host-network-ports/src.cel index 9f8cf11150..1bd9b333c1 100644 --- a/src/pod-security-policy/host-network-ports/src.cel +++ b/src/pod-security-policy/host-network-ports/src.cel @@ -21,7 +21,7 @@ variables: - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, - !(container.image in variables.exemptImages) && + !(container.image in variables.exemptImages) && has(container.ports) && ( (container.ports.all(port, has(port.hostPort) && has(variables.params.min) && port.hostPort < variables.params.min)) || (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) @@ -33,4 +33,4 @@ validations: - expression: | (has(request.operation) && request.operation == "UPDATE") || (!has(variables.params.hostNetwork) || !variables.params.hostNetwork ? (has(variables.anyObject.spec.hostNetwork) && !variables.anyObject.spec.hostNetwork) : true) - messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' \ No newline at end of file + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' diff --git a/website/docs/validation/host-network-ports.md b/website/docs/validation/host-network-ports.md index dedb8f377d..17c2b6b6ba 100644 --- a/website/docs/validation/host-network-ports.md +++ b/website/docs/validation/host-network-ports.md @@ -16,7 +16,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.1 + metadata.gatekeeper.sh/version: 1.1.2 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and @@ -84,7 +84,7 @@ spec: - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, - !(container.image in variables.exemptImages) && + !(container.image in variables.exemptImages) && has(container.ports) && ( (container.ports.all(port, has(port.hostPort) && has(variables.params.min) && port.hostPort < variables.params.min)) || (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) @@ -290,6 +290,31 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/disallowed_ephemeral.yaml ``` + +
+no-ports-specified + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports.yaml +``` +
From 5153330c861ea3865e04f76f216a7c44db9e8bd0 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Tue, 27 Aug 2024 10:36:41 -0700 Subject: [PATCH 09/20] feat(k8sdisallowanonymous): allow preventing system:authenticated (#579) Previously, the k8sdisallowanonymous would prevent bindings to the system:anonymous and system:unauthenticated groups. For Google Kubernetes Engine, the system:authenticated group is a potential security threat. This was described by Orca Security in this blog post: https://orca.security/resources/blog/sys-all-google-kubernetes-engine-risk/ This PR updates the k8sdisallowanonymous to prevent bindings to `system:authenticated` if the parameters.disallowAuthenticated toggle is set to true. This will not break existing customers as the default boolean value is false, leaving this functionality disabled. Signed-off-by: juliankatz --- .../1.1.0/artifacthub-pkg.yml | 22 ++++ .../1.1.0/kustomization.yaml | 2 + .../no-anonymous-bindings/constraint.yaml | 14 +++ .../example_allowed.yaml | 15 +++ .../example_disallowed.yaml | 18 ++++ .../samples/no-authenticated/constraint.yaml | 13 +++ .../no-authenticated/example_disallowed.yaml | 12 +++ .../disallowanonymous/1.1.0/suite.yaml | 35 ++++++ .../disallowanonymous/1.1.0/template.yaml | 68 ++++++++++++ .../example_disallowed.yaml | 3 + .../samples/no-authenticated/constraint.yaml | 13 +++ library/general/disallowanonymous/suite.yaml | 20 +++- .../general/disallowanonymous/template.yaml | 34 ++++-- src/general/disallowanonymous/constraint.tmpl | 8 +- src/general/disallowanonymous/src.rego | 26 +++-- src/general/disallowanonymous/src_test.rego | 34 ++++-- website/docs/validation/disallowanonymous.md | 102 ++++++++++++++++-- 17 files changed, 407 insertions(+), 32 deletions(-) create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/artifacthub-pkg.yml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/kustomization.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/constraint.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_allowed.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_disallowed.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/constraint.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/example_disallowed.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/suite.yaml create mode 100644 artifacthub/library/general/disallowanonymous/1.1.0/template.yaml create mode 100644 library/general/disallowanonymous/samples/no-authenticated/constraint.yaml diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/artifacthub-pkg.yml b/artifacthub/library/general/disallowanonymous/1.1.0/artifacthub-pkg.yml new file mode 100644 index 0000000000..30b8be4dad --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.0 +name: k8sdisallowanonymous +displayName: Disallow Anonymous Access +createdAt: "2024-08-21T21:13:49Z" +description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. +digest: 8de75985d7841ab683cf095d1b86934a3d3278b20816c0bb2a10680d25ddd740 +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/disallowanonymous +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Disallow Anonymous Access + Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/general/disallowanonymous/1.1.0/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/kustomization.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/constraint.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/constraint.yaml new file mode 100644 index 0000000000..1d14ae1333 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/constraint.yaml @@ -0,0 +1,14 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sDisallowAnonymous +metadata: + name: no-anonymous +spec: + match: + kinds: + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["ClusterRoleBinding"] + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["RoleBinding"] + parameters: + allowedRoles: + - cluster-role-1 diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_allowed.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_allowed.yaml new file mode 100644 index 0000000000..f8889d941d --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_allowed.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-1 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:unauthenticated diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_disallowed.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_disallowed.yaml new file mode 100644 index 0000000000..3a1fc9d050 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-anonymous-bindings/example_disallowed.yaml @@ -0,0 +1,18 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-2 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:unauthenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:anonymous diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/constraint.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/constraint.yaml new file mode 100644 index 0000000000..f4a74f4fbb --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sDisallowAnonymous +metadata: + name: no-anonymous +spec: + match: + kinds: + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["ClusterRoleBinding"] + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["RoleBinding"] + parameters: + disallowAuthenticated: true diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/example_disallowed.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/example_disallowed.yaml new file mode 100644 index 0000000000..804798e2c2 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/samples/no-authenticated/example_disallowed.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-2 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/suite.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/suite.yaml new file mode 100644 index 0000000000..e4664ec71e --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/suite.yaml @@ -0,0 +1,35 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: disallowanonymous +tests: +- name: disallow-anonymous + template: template.yaml + constraint: samples/no-anonymous-bindings/constraint.yaml + cases: + - name: example-allowed + object: samples/no-anonymous-bindings/example_allowed.yaml + assertions: + - violations: no + - name: example-disallowed + object: samples/no-anonymous-bindings/example_disallowed.yaml + assertions: + - message: "system:unauthenticated" + violations: 1 + - message: "system:anonymous" + violations: 1 + - message: "system:authenticated" + violations: 0 +- name: disallow-authenticated + template: template.yaml + constraint: samples/no-authenticated/constraint.yaml + cases: + - name: authenticated-disallowed-with-parameter-true + object: samples/no-anonymous-bindings/example_disallowed.yaml + assertions: + - message: "system:unauthenticated" + violations: 1 + - message: "system:anonymous" + violations: 1 + - message: "system:authenticated" + violations: 1 diff --git a/artifacthub/library/general/disallowanonymous/1.1.0/template.yaml b/artifacthub/library/general/disallowanonymous/1.1.0/template.yaml new file mode 100644 index 0000000000..deb410d6c4 --- /dev/null +++ b/artifacthub/library/general/disallowanonymous/1.1.0/template.yaml @@ -0,0 +1,68 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8sdisallowanonymous + annotations: + metadata.gatekeeper.sh/title: "Disallow Anonymous Access" + metadata.gatekeeper.sh/version: 1.1.0 + description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. +spec: + crd: + spec: + names: + kind: K8sDisallowAnonymous + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + properties: + allowedRoles: + description: >- + The list of ClusterRoles and Roles that may be associated + with the `system:unauthenticated` group and `system:anonymous` + user. + type: array + items: + type: string + disallowAuthenticated: + description: >- + A boolean indicating whether `system:authenticated` should also + be disallowed by this policy. + type: boolean + default: false + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8sdisallowanonymous + + violation[{"msg": msg}] { + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) + + group := ["system:unauthenticated", "system:anonymous"][_] + subject_is(input.review.object.subjects[_], group) + + msg := message(group) + } + + violation[{"msg": msg}] { + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) + + object.get(input, ["parameters", "disallowAuthenticated"], false) + + group := "system:authenticated" + subject_is(input.review.object.subjects[_], group) + + msg := message(group) + } + + is_allowed(role, allowedRoles) { + role.name == allowedRoles[_] + } + + subject_is(subject, expected) { + subject.name == expected + } + + message(name) := val { + val := sprintf("%v is not allowed as a subject name in %v %v", [name, input.review.object.kind, input.review.object.metadata.name]) + } diff --git a/library/general/disallowanonymous/samples/no-anonymous-bindings/example_disallowed.yaml b/library/general/disallowanonymous/samples/no-anonymous-bindings/example_disallowed.yaml index 1f34ffd642..3a1fc9d050 100644 --- a/library/general/disallowanonymous/samples/no-anonymous-bindings/example_disallowed.yaml +++ b/library/general/disallowanonymous/samples/no-anonymous-bindings/example_disallowed.yaml @@ -13,3 +13,6 @@ subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:unauthenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:anonymous diff --git a/library/general/disallowanonymous/samples/no-authenticated/constraint.yaml b/library/general/disallowanonymous/samples/no-authenticated/constraint.yaml new file mode 100644 index 0000000000..f4a74f4fbb --- /dev/null +++ b/library/general/disallowanonymous/samples/no-authenticated/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sDisallowAnonymous +metadata: + name: no-anonymous +spec: + match: + kinds: + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["ClusterRoleBinding"] + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["RoleBinding"] + parameters: + disallowAuthenticated: true diff --git a/library/general/disallowanonymous/suite.yaml b/library/general/disallowanonymous/suite.yaml index 46daa7f2fb..e4664ec71e 100644 --- a/library/general/disallowanonymous/suite.yaml +++ b/library/general/disallowanonymous/suite.yaml @@ -14,4 +14,22 @@ tests: - name: example-disallowed object: samples/no-anonymous-bindings/example_disallowed.yaml assertions: - - violations: yes \ No newline at end of file + - message: "system:unauthenticated" + violations: 1 + - message: "system:anonymous" + violations: 1 + - message: "system:authenticated" + violations: 0 +- name: disallow-authenticated + template: template.yaml + constraint: samples/no-authenticated/constraint.yaml + cases: + - name: authenticated-disallowed-with-parameter-true + object: samples/no-anonymous-bindings/example_disallowed.yaml + assertions: + - message: "system:unauthenticated" + violations: 1 + - message: "system:anonymous" + violations: 1 + - message: "system:authenticated" + violations: 1 diff --git a/library/general/disallowanonymous/template.yaml b/library/general/disallowanonymous/template.yaml index 1f090f3521..deb410d6c4 100644 --- a/library/general/disallowanonymous/template.yaml +++ b/library/general/disallowanonymous/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8sdisallowanonymous annotations: metadata.gatekeeper.sh/title: "Disallow Anonymous Access" - metadata.gatekeeper.sh/version: 1.0.1 + metadata.gatekeeper.sh/version: 1.1.0 description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. spec: crd: @@ -24,6 +24,12 @@ spec: type: array items: type: string + disallowAuthenticated: + description: >- + A boolean indicating whether `system:authenticated` should also + be disallowed by this policy. + type: boolean + default: false targets: - target: admission.k8s.gatekeeper.sh rego: | @@ -31,18 +37,32 @@ spec: violation[{"msg": msg}] { not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) - review(input.review.object.subjects[_]) - msg := sprintf("Unauthenticated user reference is not allowed in %v %v ", [input.review.object.kind, input.review.object.metadata.name]) + + group := ["system:unauthenticated", "system:anonymous"][_] + subject_is(input.review.object.subjects[_], group) + + msg := message(group) + } + + violation[{"msg": msg}] { + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) + + object.get(input, ["parameters", "disallowAuthenticated"], false) + + group := "system:authenticated" + subject_is(input.review.object.subjects[_], group) + + msg := message(group) } is_allowed(role, allowedRoles) { role.name == allowedRoles[_] } - review(subject) = true { - subject.name == "system:unauthenticated" + subject_is(subject, expected) { + subject.name == expected } - review(subject) = true { - subject.name == "system:anonymous" + message(name) := val { + val := sprintf("%v is not allowed as a subject name in %v %v", [name, input.review.object.kind, input.review.object.metadata.name]) } diff --git a/src/general/disallowanonymous/constraint.tmpl b/src/general/disallowanonymous/constraint.tmpl index ff7caf000e..a2bdb1136f 100644 --- a/src/general/disallowanonymous/constraint.tmpl +++ b/src/general/disallowanonymous/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8sdisallowanonymous annotations: metadata.gatekeeper.sh/title: "Disallow Anonymous Access" - metadata.gatekeeper.sh/version: 1.0.1 + metadata.gatekeeper.sh/version: 1.1.0 description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. spec: crd: @@ -24,6 +24,12 @@ spec: type: array items: type: string + disallowAuthenticated: + description: >- + A boolean indicating whether `system:authenticated` should also + be disallowed by this policy. + type: boolean + default: false targets: - target: admission.k8s.gatekeeper.sh rego: | diff --git a/src/general/disallowanonymous/src.rego b/src/general/disallowanonymous/src.rego index fee795a3dc..0361db26f8 100644 --- a/src/general/disallowanonymous/src.rego +++ b/src/general/disallowanonymous/src.rego @@ -2,18 +2,32 @@ package k8sdisallowanonymous violation[{"msg": msg}] { not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) - review(input.review.object.subjects[_]) - msg := sprintf("Unauthenticated user reference is not allowed in %v %v ", [input.review.object.kind, input.review.object.metadata.name]) + + group := ["system:unauthenticated", "system:anonymous"][_] + subject_is(input.review.object.subjects[_], group) + + msg := message(group) +} + +violation[{"msg": msg}] { + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) + + object.get(input, ["parameters", "disallowAuthenticated"], false) + + group := "system:authenticated" + subject_is(input.review.object.subjects[_], group) + + msg := message(group) } is_allowed(role, allowedRoles) { role.name == allowedRoles[_] } -review(subject) = true { - subject.name == "system:unauthenticated" +subject_is(subject, expected) { + subject.name == expected } -review(subject) = true { - subject.name == "system:anonymous" +message(name) := val { + val := sprintf("%v is not allowed as a subject name in %v %v", [name, input.review.object.kind, input.review.object.metadata.name]) } diff --git a/src/general/disallowanonymous/src_test.rego b/src/general/disallowanonymous/src_test.rego index ff951c9b35..fbc8e87a94 100644 --- a/src/general/disallowanonymous/src_test.rego +++ b/src/general/disallowanonymous/src_test.rego @@ -6,12 +6,6 @@ test_blank_subject_clusterrolebinding { count(results) == 0 } -test_authenticated_group_clusterrolebinding { - inp := {"review": clusterrolebinding([{"name": "system:authenticated", "kind": "Group"}], "role-2"), "parameters": {"allowedRoles": ["role-1"]}} - results := violation with input as inp - count(results) == 0 -} - test_non_anonymous_user_clusterrolebinding { inp := {"review": clusterrolebinding([{"name": "user-1", "kind": "User"}], "role-2"), "parameters": {"allowedRoles": ["role-1"]}} results := violation with input as inp @@ -105,7 +99,7 @@ test_allowed_multiple_role_multiple_subjects_unauthenticated_group_clusterrolebi test_multiple_subjects_mix_clusterrolebinding { inp := {"review": clusterrolebinding([{"name": "system:unauthenticated", "kind": "Group"}, {"name": "system:anonymous", "kind": "User"}], "role-2"), "parameters": {"allowedRoles": ["role-1"]}} results := violation with input as inp - count(results) == 1 + count(results) == 2 } test_allowed_role_multiple_subjects_mix_clusterrolebinding { @@ -117,7 +111,7 @@ test_allowed_role_multiple_subjects_mix_clusterrolebinding { test_multiple_role_multiple_subjects_mix_clusterrolebinding { inp := {"review": clusterrolebinding([{"name": "system:unauthenticated", "kind": "Group"}, {"name": "system:anonymous", "kind": "User"}], "role-2"), "parameters": {"allowedRoles": ["role-1", "role-3"]}} results := violation with input as inp - count(results) == 1 + count(results) == 2 } test_allowed_multiple_role_multiple_subjects_mix_clusterrolebinding { @@ -225,7 +219,7 @@ test_allowed_multiple_role_multiple_subjects_unauthenticated_group_rolebinding { test_multiple_subjects_mix_rolebinding { inp := {"review": rolebinding([{"name": "system:unauthenticated", "kind": "Group"}, {"name": "system:anonymous", "kind": "User"}], "role-2"), "parameters": {"allowedRoles": ["role-1"]}} results := violation with input as inp - count(results) == 1 + count(results) == 2 } test_allowed_role_multiple_subjects_mix_rolebinding { @@ -237,7 +231,7 @@ test_allowed_role_multiple_subjects_mix_rolebinding { test_multiple_role_multiple_subjects_mix_rolebinding { inp := {"review": rolebinding([{"name": "system:unauthenticated", "kind": "Group"}, {"name": "system:anonymous", "kind": "User"}], "role-2"), "parameters": {"allowedRoles": ["role-1", "role-3"]}} results := violation with input as inp - count(results) == 1 + count(results) == 2 } test_allowed_multiple_role_multiple_subjects_mix_rolebinding { @@ -246,6 +240,26 @@ test_allowed_multiple_role_multiple_subjects_mix_rolebinding { count(results) == 0 } +authenticated_rolebinding := rolebinding([{"name": "system:authenticated", "kind": "Group"}], "role-2") + +test_authenticated_with_disallow_authenticated_true { + inp := {"review": authenticated_rolebinding, "parameters": { "allowedRoles": [], "disallowAuthenticated": true }} + results := violation with input as inp + count(results) == 1 +} + +test_authenticated_with_disallow_authenticated_false { + inp := {"review": authenticated_rolebinding, "parameters": { "allowedRoles": [], "disallowAuthenticated": false }} + results := violation with input as inp + count(results) == 0 +} + +test_authenticated_with_disallow_authenticated_undefined { + inp := {"review": authenticated_rolebinding, "parameters": { "allowedRoles": [] }} + results := violation with input as inp + count(results) == 0 +} + clusterrolebinding(subjects, roleref_name) = { "object": { "kind": "ClusterRoleBinding", diff --git a/website/docs/validation/disallowanonymous.md b/website/docs/validation/disallowanonymous.md index 12526a9a0d..bdf251922c 100644 --- a/website/docs/validation/disallowanonymous.md +++ b/website/docs/validation/disallowanonymous.md @@ -16,7 +16,7 @@ metadata: name: k8sdisallowanonymous annotations: metadata.gatekeeper.sh/title: "Disallow Anonymous Access" - metadata.gatekeeper.sh/version: 1.0.1 + metadata.gatekeeper.sh/version: 1.1.0 description: Disallows associating ClusterRole and Role resources to the system:anonymous user and system:unauthenticated group. spec: crd: @@ -36,6 +36,12 @@ spec: type: array items: type: string + disallowAuthenticated: + description: >- + A boolean indicating whether `system:authenticated` should also + be disallowed by this policy. + type: boolean + default: false targets: - target: admission.k8s.gatekeeper.sh rego: | @@ -43,20 +49,34 @@ spec: violation[{"msg": msg}] { not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) - review(input.review.object.subjects[_]) - msg := sprintf("Unauthenticated user reference is not allowed in %v %v ", [input.review.object.kind, input.review.object.metadata.name]) + + group := ["system:unauthenticated", "system:anonymous"][_] + subject_is(input.review.object.subjects[_], group) + + msg := message(group) + } + + violation[{"msg": msg}] { + not is_allowed(input.review.object.roleRef, object.get(input, ["parameters", "allowedRoles"], [])) + + object.get(input, ["parameters", "disallowAuthenticated"], false) + + group := "system:authenticated" + subject_is(input.review.object.subjects[_], group) + + msg := message(group) } is_allowed(role, allowedRoles) { role.name == allowedRoles[_] } - review(subject) = true { - subject.name == "system:unauthenticated" + subject_is(subject, expected) { + subject.name == expected } - review(subject) = true { - subject.name == "system:anonymous" + message(name) := val { + val := sprintf("%v is not allowed as a subject name in %v %v", [name, input.review.object.kind, input.review.object.metadata.name]) } ``` @@ -146,6 +166,74 @@ subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:unauthenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:anonymous + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/disallowanonymous/samples/no-anonymous-bindings/example_disallowed.yaml +``` + + + + +
+disallow-authenticated + +
+constraint + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sDisallowAnonymous +metadata: + name: no-anonymous +spec: + match: + kinds: + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["ClusterRoleBinding"] + - apiGroups: ["rbac.authorization.k8s.io"] + kinds: ["RoleBinding"] + parameters: + disallowAuthenticated: true + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/disallowanonymous/samples/no-authenticated/constraint.yaml +``` + +
+ +
+authenticated-disallowed-with-parameter-true + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-role-binding-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-role-2 +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:unauthenticated +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:anonymous ``` From 3acd611596720a1e23c71208f00f3458c3566928 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Thu, 29 Aug 2024 13:41:48 -0700 Subject: [PATCH 10/20] chore(k8spspallowprivilegeescalationcontainer): suite test confirming exemptImages (#587) I recently found (#584) that some K8sNativeValidation implementations of certain templates that iterate over and exempt containers by image had a bug preventing the exemption logic from working. The k8spspallowprivilegeescalationcontainer turns out not to have this problem, as proved by the passing tests with the addition of an image exemption suite test. Signed-off-by: juliankatz --- .../constraint.yaml | 2 +- .../example_allowed_exempt.yaml | 12 ++++++++ .../1.1.0/suite.yaml | 4 +++ .../constraint.yaml | 2 +- .../example_allowed_exempt.yaml | 12 ++++++++ .../allow-privilege-escalation/suite.yaml | 4 +++ .../validation/allow-privilege-escalation.md | 28 ++++++++++++++++++- 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml create mode 100644 library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.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 index 1d4f031c40..cfa512d9d6 100644 --- 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 @@ -8,4 +8,4 @@ spec: - apiGroups: [""] kinds: ["Pod"] parameters: - exemptImages: ["nonexistent/*"] + exemptImages: ["safeimages.com/*"] diff --git a/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml new file mode 100644 index 0000000000..841e56f892 --- /dev/null +++ b/artifacthub/library/pod-security-policy/allow-privilege-escalation/1.1.0/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.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: "safeimages.com/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 index cd7531cfa4..345a7582ca 100644 --- 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 @@ -23,3 +23,7 @@ tests: object: samples/psp-allow-privilege-escalation-container/update.yaml assertions: - violations: no + - name: exempted-path + object: samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml + assertions: + - violations: no 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 1d4f031c40..cfa512d9d6 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 @@ -8,4 +8,4 @@ spec: - apiGroups: [""] kinds: ["Pod"] parameters: - exemptImages: ["nonexistent/*"] + exemptImages: ["safeimages.com/*"] diff --git a/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml b/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml new file mode 100644 index 0000000000..841e56f892 --- /dev/null +++ b/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.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: "safeimages.com/nginx" + securityContext: + allowPrivilegeEscalation: true diff --git a/library/pod-security-policy/allow-privilege-escalation/suite.yaml b/library/pod-security-policy/allow-privilege-escalation/suite.yaml index cd7531cfa4..345a7582ca 100644 --- a/library/pod-security-policy/allow-privilege-escalation/suite.yaml +++ b/library/pod-security-policy/allow-privilege-escalation/suite.yaml @@ -23,3 +23,7 @@ tests: object: samples/psp-allow-privilege-escalation-container/update.yaml assertions: - violations: no + - name: exempted-path + object: samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml + assertions: + - violations: no diff --git a/website/docs/validation/allow-privilege-escalation.md b/website/docs/validation/allow-privilege-escalation.md index e36f366889..66d6c037ef 100644 --- a/website/docs/validation/allow-privilege-escalation.md +++ b/website/docs/validation/allow-privilege-escalation.md @@ -173,7 +173,7 @@ spec: - apiGroups: [""] kinds: ["Pod"] parameters: - exemptImages: ["nonexistent/*"] + exemptImages: ["safeimages.com/*"] ``` @@ -262,6 +262,32 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/disallowed_ephemeral.yaml ``` +
+
+exempted-path + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privilege-escalation-disallowed + labels: + app: nginx-privilege-escalation +spec: + containers: + - name: nginx + image: "safeimages.com/nginx" + securityContext: + allowPrivilegeEscalation: true + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/allow-privilege-escalation/samples/psp-allow-privilege-escalation-container/example_allowed_exempt.yaml +``` +
From 9cf3312cc0902b8c3cf940b72a3f198011d37d0a Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Thu, 29 Aug 2024 14:21:41 -0700 Subject: [PATCH 11/20] fix(k8spspreadonlyrootfilesystem): CEL support wildcard in exemptImages (#584) Despite it being non-sensical to put a `*` in exemptImages (functionally disabling the policy), this is supported in the existing rego implementation of the template. Thus, not doing it in the CEL implementation is an inconsistency and a breaking change. This PR upholds the contract by adding support for `*` as an exemptImage. Signed-off-by: juliankatz Co-authored-by: Andrew Peabody --- .../1.1.1/artifacthub-pkg.yml | 22 +++ .../1.1.1/kustomization.yaml | 2 + .../samples/full_wildcard/constraint.yaml | 12 ++ .../constraint.yaml | 12 ++ .../disallowed_ephemeral.yaml | 12 ++ .../example_allowed.yaml | 12 ++ .../example_allowed_exempted.yaml | 12 ++ .../example_disallowed.yaml | 12 ++ .../psp-readonlyrootfilesystem/update.yaml | 17 ++ .../samples/wildcard-prefix/constraint.yaml | 12 ++ .../example_allowed_safe_prefix.yaml | 12 ++ .../example_disallowed_unsafe_prefix.yaml | 12 ++ .../1.1.1/suite.yaml | 51 +++++ .../1.1.1/template.yaml | 140 ++++++++++++++ .../samples/full_wildcard/constraint.yaml | 12 ++ .../constraint.yaml | 3 + .../example_allowed_exempted.yaml | 12 ++ .../samples/wildcard-prefix/constraint.yaml | 12 ++ .../example_allowed_safe_prefix.yaml | 12 ++ .../example_disallowed_unsafe_prefix.yaml | 12 ++ .../read-only-root-filesystem/suite.yaml | 24 +++ .../read-only-root-filesystem/template.yaml | 4 +- .../read-only-root-filesystem/constraint.tmpl | 2 +- .../read-only-root-filesystem/src.cel | 2 +- .../validation/read-only-root-filesystem.md | 175 +++++++++++++++++- 25 files changed, 604 insertions(+), 6 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/full_wildcard/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/update.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_allowed_safe_prefix.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/template.yaml create mode 100644 library/pod-security-policy/read-only-root-filesystem/samples/full_wildcard/constraint.yaml create mode 100644 library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml create mode 100644 library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/constraint.yaml create mode 100644 library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_allowed_safe_prefix.yaml create mode 100644 library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/artifacthub-pkg.yml new file mode 100644 index 0000000000..297b500602 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.1 +name: k8spspreadonlyrootfilesystem +displayName: Read Only Root Filesystem +createdAt: "2024-08-29T20:26:02Z" +description: Requires the use of a read-only root file system by pod containers. Corresponds to the `readOnlyRootFilesystem` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems +digest: 9ec2743dce71b385161873179a1d70b6f801f72b077bd178334f75f2ee1023bd +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/read-only-root-filesystem +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Read Only Root Filesystem + Requires the use of a read-only root file system by pod containers. Corresponds to the `readOnlyRootFilesystem` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/kustomization.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/full_wildcard/constraint.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/full_wildcard/constraint.yaml new file mode 100644 index 0000000000..b5497e7611 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/full_wildcard/constraint.yaml @@ -0,0 +1,12 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPReadOnlyRootFilesystem +metadata: + name: psp-readonlyrootfilesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: + - "*" diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/constraint.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/constraint.yaml new file mode 100644 index 0000000000..d32dc33ab7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/constraint.yaml @@ -0,0 +1,12 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPReadOnlyRootFilesystem +metadata: + name: psp-readonlyrootfilesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: + - "specialprogram" diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..d0ce2c4dc5 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/disallowed_ephemeral.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-disallowed + labels: + app: nginx-readonlyrootfilesystem +spec: + ephemeralContainers: + - name: nginx + image: nginx + securityContext: + readOnlyRootFilesystem: false diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed.yaml new file mode 100644 index 0000000000..9c96bd18cd --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-allowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: nginx + securityContext: + readOnlyRootFilesystem: true diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml new file mode 100644 index 0000000000..1f86b59aed --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-disallowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: specialprogram + image: specialprogram + securityContext: + readOnlyRootFilesystem: false diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_disallowed.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_disallowed.yaml new file mode 100644 index 0000000000..7571bfd9fb --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/example_disallowed.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-disallowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: nginx + securityContext: + readOnlyRootFilesystem: false diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/update.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/update.yaml new file mode 100644 index 0000000000..b31ae5e3a8 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/psp-readonlyrootfilesystem/update.yaml @@ -0,0 +1,17 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: nginx-readonlyrootfilesystem-disallowed + labels: + app: nginx-readonlyrootfilesystem + spec: + containers: + - name: nginx + image: nginx + securityContext: + readOnlyRootFilesystem: false diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/constraint.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/constraint.yaml new file mode 100644 index 0000000000..8bac931409 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/constraint.yaml @@ -0,0 +1,12 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPReadOnlyRootFilesystem +metadata: + name: psp-readonlyrootfilesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: + - "safe-images.com/*" diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_allowed_safe_prefix.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_allowed_safe_prefix.yaml new file mode 100644 index 0000000000..6c4c60d104 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_allowed_safe_prefix.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-allowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: "safe-images.com/nginx" + securityContext: + readOnlyRootFilesystem: false diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml new file mode 100644 index 0000000000..563abb05f7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-allowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: "unsafe-images.com/nginx" + securityContext: + readOnlyRootFilesystem: false diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/suite.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/suite.yaml new file mode 100644 index 0000000000..bb064b179f --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/suite.yaml @@ -0,0 +1,51 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: read-only-root-filesystem +tests: +- name: require-read-only-root-filesystem + template: template.yaml + constraint: samples/psp-readonlyrootfilesystem/constraint.yaml + cases: + - name: example-disallowed + object: samples/psp-readonlyrootfilesystem/example_disallowed.yaml + assertions: + - violations: yes + message: "only read-only root filesystem container is allowed: nginx" + - name: example-allowed + object: samples/psp-readonlyrootfilesystem/example_allowed.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-readonlyrootfilesystem/disallowed_ephemeral.yaml + assertions: + - violations: yes + message: "only read-only root filesystem container is allowed: nginx" + - name: update + object: samples/psp-readonlyrootfilesystem/update.yaml + assertions: + - violations: no + - name: exact-exemption + object: samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml + assertions: + - violations: no +- name: full-wildcard + template: template.yaml + constraint: samples/full_wildcard/constraint.yaml + cases: + - name: allow-normally-disallowed + object: samples/psp-readonlyrootfilesystem/example_disallowed.yaml + assertions: + - violations: no +- name: wildcard-prefix + template: template.yaml + constraint: samples/wildcard-prefix/constraint.yaml + cases: + - name: image-with-exempt-prefix-readOnlyRootFilesystem-not-required + object: samples/wildcard-prefix/example_allowed_safe_prefix.yaml + assertions: + - violations: no + - name: image-with-different-prefix-must-set-readOnlyRootFilesystem + object: samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml + assertions: + - violations: yes diff --git a/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/template.yaml b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/template.yaml new file mode 100644 index 0000000000..7f3a402e80 --- /dev/null +++ b/artifacthub/library/pod-security-policy/read-only-root-filesystem/1.1.1/template.yaml @@ -0,0 +1,140 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spspreadonlyrootfilesystem + annotations: + metadata.gatekeeper.sh/title: "Read Only Root Filesystem" + metadata.gatekeeper.sh/version: 1.1.1 + description: >- + Requires the use of a read-only root file system by pod containers. + Corresponds to the `readOnlyRootFilesystem` field in a + PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems +spec: + crd: + spec: + names: + kind: K8sPSPReadOnlyRootFilesystem + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Requires the use of a read-only root file system by pod containers. + Corresponds to the `readOnlyRootFilesystem` field in a + PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems + 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.readOnlyRootFilesystem) || + container.securityContext.readOnlyRootFilesystem != true) + ).map(container, container.name) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + messageExpression: '"only read-only root filesystem container is allowed: " + variables.badContainers.join(", ")' + + - engine: Rego + source: + rego: | + package k8spspreadonlyrootfilesystem + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.containers.readOnlyRootFilesystem field is immutable. + not is_update(input.review) + + c := input_containers[_] + not is_exempt(c) + input_read_only_root_fs(c) + msg := sprintf("only read-only root filesystem container is allowed: %v", [c.name]) + } + + input_read_only_root_fs(c) { + not has_field(c, "securityContext") + } + input_read_only_root_fs(c) { + not c.securityContext.readOnlyRootFilesystem == true + } + + 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/read-only-root-filesystem/samples/full_wildcard/constraint.yaml b/library/pod-security-policy/read-only-root-filesystem/samples/full_wildcard/constraint.yaml new file mode 100644 index 0000000000..b5497e7611 --- /dev/null +++ b/library/pod-security-policy/read-only-root-filesystem/samples/full_wildcard/constraint.yaml @@ -0,0 +1,12 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPReadOnlyRootFilesystem +metadata: + name: psp-readonlyrootfilesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: + - "*" diff --git a/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/constraint.yaml b/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/constraint.yaml index 66d6bdabe9..d32dc33ab7 100644 --- a/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/constraint.yaml +++ b/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/constraint.yaml @@ -7,3 +7,6 @@ spec: kinds: - apiGroups: [""] kinds: ["Pod"] + parameters: + exemptImages: + - "specialprogram" diff --git a/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml b/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml new file mode 100644 index 0000000000..1f86b59aed --- /dev/null +++ b/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-disallowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: specialprogram + image: specialprogram + securityContext: + readOnlyRootFilesystem: false diff --git a/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/constraint.yaml b/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/constraint.yaml new file mode 100644 index 0000000000..8bac931409 --- /dev/null +++ b/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/constraint.yaml @@ -0,0 +1,12 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPReadOnlyRootFilesystem +metadata: + name: psp-readonlyrootfilesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: + - "safe-images.com/*" diff --git a/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_allowed_safe_prefix.yaml b/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_allowed_safe_prefix.yaml new file mode 100644 index 0000000000..6c4c60d104 --- /dev/null +++ b/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_allowed_safe_prefix.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-allowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: "safe-images.com/nginx" + securityContext: + readOnlyRootFilesystem: false diff --git a/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml b/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml new file mode 100644 index 0000000000..563abb05f7 --- /dev/null +++ b/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-allowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: "unsafe-images.com/nginx" + securityContext: + readOnlyRootFilesystem: false diff --git a/library/pod-security-policy/read-only-root-filesystem/suite.yaml b/library/pod-security-policy/read-only-root-filesystem/suite.yaml index fb52faa007..bb064b179f 100644 --- a/library/pod-security-policy/read-only-root-filesystem/suite.yaml +++ b/library/pod-security-policy/read-only-root-filesystem/suite.yaml @@ -25,3 +25,27 @@ tests: object: samples/psp-readonlyrootfilesystem/update.yaml assertions: - violations: no + - name: exact-exemption + object: samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml + assertions: + - violations: no +- name: full-wildcard + template: template.yaml + constraint: samples/full_wildcard/constraint.yaml + cases: + - name: allow-normally-disallowed + object: samples/psp-readonlyrootfilesystem/example_disallowed.yaml + assertions: + - violations: no +- name: wildcard-prefix + template: template.yaml + constraint: samples/wildcard-prefix/constraint.yaml + cases: + - name: image-with-exempt-prefix-readOnlyRootFilesystem-not-required + object: samples/wildcard-prefix/example_allowed_safe_prefix.yaml + assertions: + - violations: no + - name: image-with-different-prefix-must-set-readOnlyRootFilesystem + object: samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml + assertions: + - violations: yes diff --git a/library/pod-security-policy/read-only-root-filesystem/template.yaml b/library/pod-security-policy/read-only-root-filesystem/template.yaml index 361ff999d5..7f3a402e80 100644 --- a/library/pod-security-policy/read-only-root-filesystem/template.yaml +++ b/library/pod-security-policy/read-only-root-filesystem/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spspreadonlyrootfilesystem annotations: metadata.gatekeeper.sh/title: "Read Only Root Filesystem" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Requires the use of a read-only root file system by pod containers. Corresponds to the `readOnlyRootFilesystem` field in a @@ -59,7 +59,7 @@ spec: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, diff --git a/src/pod-security-policy/read-only-root-filesystem/constraint.tmpl b/src/pod-security-policy/read-only-root-filesystem/constraint.tmpl index 862dfa51cd..57516f3d88 100644 --- a/src/pod-security-policy/read-only-root-filesystem/constraint.tmpl +++ b/src/pod-security-policy/read-only-root-filesystem/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spspreadonlyrootfilesystem annotations: metadata.gatekeeper.sh/title: "Read Only Root Filesystem" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Requires the use of a read-only root file system by pod containers. Corresponds to the `readOnlyRootFilesystem` field in a diff --git a/src/pod-security-policy/read-only-root-filesystem/src.cel b/src/pod-security-policy/read-only-root-filesystem/src.cel index 0c4db172b4..0be247c12a 100644 --- a/src/pod-security-policy/read-only-root-filesystem/src.cel +++ b/src/pod-security-policy/read-only-root-filesystem/src.cel @@ -17,7 +17,7 @@ variables: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, diff --git a/website/docs/validation/read-only-root-filesystem.md b/website/docs/validation/read-only-root-filesystem.md index 038698b2ae..4a841832f6 100644 --- a/website/docs/validation/read-only-root-filesystem.md +++ b/website/docs/validation/read-only-root-filesystem.md @@ -16,7 +16,7 @@ metadata: name: k8spspreadonlyrootfilesystem annotations: metadata.gatekeeper.sh/title: "Read Only Root Filesystem" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Requires the use of a read-only root file system by pod containers. Corresponds to the `readOnlyRootFilesystem` field in a @@ -71,7 +71,7 @@ spec: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, @@ -174,6 +174,9 @@ spec: kinds: - apiGroups: [""] kinds: ["Pod"] + parameters: + exemptImages: + - "specialprogram" ``` @@ -262,6 +265,174 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/disallowed_ephemeral.yaml ``` +
+
+exact-exemption + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-disallowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: specialprogram + image: specialprogram + securityContext: + readOnlyRootFilesystem: false + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/example_allowed_exempted.yaml +``` + +
+ + +
+full-wildcard + +
+constraint + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPReadOnlyRootFilesystem +metadata: + name: psp-readonlyrootfilesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: + - "*" + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/read-only-root-filesystem/samples/full_wildcard/constraint.yaml +``` + +
+ +
+allow-normally-disallowed + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-disallowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: nginx + securityContext: + readOnlyRootFilesystem: false + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/read-only-root-filesystem/samples/psp-readonlyrootfilesystem/example_disallowed.yaml +``` + +
+ + +
+wildcard-prefix + +
+constraint + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPReadOnlyRootFilesystem +metadata: + name: psp-readonlyrootfilesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + exemptImages: + - "safe-images.com/*" + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/constraint.yaml +``` + +
+ +
+image-with-exempt-prefix-readOnlyRootFilesystem-not-required + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-allowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: "safe-images.com/nginx" + securityContext: + readOnlyRootFilesystem: false + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_allowed_safe_prefix.yaml +``` + +
+
+image-with-different-prefix-must-set-readOnlyRootFilesystem + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-readonlyrootfilesystem-allowed + labels: + app: nginx-readonlyrootfilesystem +spec: + containers: + - name: nginx + image: "unsafe-images.com/nginx" + securityContext: + readOnlyRootFilesystem: false + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/read-only-root-filesystem/samples/wildcard-prefix/example_disallowed_unsafe_prefix.yaml +``` +
From 888da7b46eca34eb7b66b771fdd3172f07a9fc38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:26:09 -0700 Subject: [PATCH 12/20] chore: bump golang from 1.22 to 1.23 in /build/gomplate (#576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps golang from 1.22 to 1.23. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- build/gomplate/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gomplate/Dockerfile b/build/gomplate/Dockerfile index 21e0df9750..9b3fde0df5 100644 --- a/build/gomplate/Dockerfile +++ b/build/gomplate/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22@sha256:2bd56f00ff47baf33e64eae7996b65846c7cb5e0a46e0a882ef179fd89654afa +FROM golang:1.23@sha256:613a108a4a4b1dfb6923305db791a19d088f77632317cfc3446825c54fb862cd ARG GOMPLATE_VERSION From 4f6d2f54c2e628db232730af1eac93733ea982d6 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Fri, 30 Aug 2024 13:10:48 -0700 Subject: [PATCH 13/20] fix(k8spspprocmount): fix exemptImages support (#588) I recently found (#584) that some K8sNativeValidation implementations of certain templates that iterate over and exempt containers by image had a bug preventing the exemption logic from working. I've fixed that bug here by mapping from `container` struct to `container.image` string. I've also added a suite test to verify this. That case fails without the change to the CEL logic. Signed-off-by: juliankatz --- .../proc-mount/1.1.1/README.md | 12 ++ .../proc-mount/1.1.1/artifacthub-pkg.yml | 22 +++ .../proc-mount/1.1.1/kustomization.yaml | 2 + .../samples/psp-proc-mount/constraint.yaml | 13 ++ .../psp-proc-mount/disallowed_ephemeral.yaml | 13 ++ .../psp-proc-mount/example_allowed.yaml | 13 ++ .../example_allowed_exempt_image.yaml | 13 ++ .../psp-proc-mount/example_disallowed.yaml | 13 ++ .../1.1.1/samples/psp-proc-mount/update.yaml | 17 ++ .../proc-mount/1.1.1/suite.yaml | 29 +++ .../proc-mount/1.1.1/template.yaml | 185 ++++++++++++++++++ .../samples/psp-proc-mount/constraint.yaml | 2 + .../example_allowed_exempt_image.yaml | 13 ++ .../pod-security-policy/proc-mount/suite.yaml | 4 + .../proc-mount/template.yaml | 11 +- .../proc-mount/constraint.tmpl | 2 +- src/pod-security-policy/proc-mount/src.cel | 11 +- website/docs/validation/proc-mount.md | 40 +++- 18 files changed, 405 insertions(+), 10 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/README.md create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed_exempt_image.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/update.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/proc-mount/1.1.1/template.yaml create mode 100644 library/pod-security-policy/proc-mount/samples/psp-proc-mount/example_allowed_exempt_image.yaml diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/README.md b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/README.md new file mode 100644 index 0000000000..9e45b72071 --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/README.md @@ -0,0 +1,12 @@ +# ProcMount security context policy + +`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. + +Types of proc mount are: + +- `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. + +- `UnmaskedProcMount` bypasses the default masking behavior of the container runtime and ensures the newly created /proc the container stays in tact with no modifications. + +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 +https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/#options and https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/. diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/artifacthub-pkg.yml new file mode 100644 index 0000000000..5ae52d10c5 --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.1 +name: k8spspprocmount +displayName: Proc Mount +createdAt: "2024-08-29T21:24:52Z" +description: Controls the allowed `procMount` types for the container. Corresponds to the `allowedProcMountTypes` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#allowedprocmounttypes +digest: e3a4bd2b1bf1c2401a90292cac4a479fe102f0a78dbeb6ac2bf8808f14f4ea2d +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/proc-mount +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Proc Mount + Controls the allowed `procMount` types for the container. Corresponds to the `allowedProcMountTypes` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#allowedprocmounttypes +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/proc-mount/1.1.1/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/kustomization.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/constraint.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/constraint.yaml new file mode 100644 index 0000000000..79ad221ab3 --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPProcMount +metadata: + name: psp-proc-mount +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + procMount: Default + exemptImages: + - "safeimages.com/*" diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..74e034148d --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/disallowed_ephemeral.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-proc-mount-disallowed + labels: + app: nginx-proc-mount +spec: + hostUsers: false + ephemeralContainers: + - name: nginx + image: nginx + securityContext: + procMount: Unmasked #Default diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed.yaml new file mode 100644 index 0000000000..cc272bafb8 --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-proc-mount-allowed + labels: + app: nginx-proc-mount +spec: + hostUsers: false + containers: + - name: nginx + image: nginx + securityContext: + procMount: Default diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed_exempt_image.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed_exempt_image.yaml new file mode 100644 index 0000000000..c197740a4e --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_allowed_exempt_image.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-proc-mount-exempt-image + labels: + app: nginx-proc-mount +spec: + hostUsers: false + containers: + - name: nginx + image: safeimages.com/nginx + securityContext: + procMount: Unmasked #Default diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_disallowed.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_disallowed.yaml new file mode 100644 index 0000000000..f0c3b030a8 --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/example_disallowed.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-proc-mount-disallowed + labels: + app: nginx-proc-mount +spec: + hostUsers: false + containers: + - name: nginx + image: nginx + securityContext: + procMount: Unmasked #Default diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/update.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/update.yaml new file mode 100644 index 0000000000..dc21b1142b --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/samples/psp-proc-mount/update.yaml @@ -0,0 +1,17 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: nginx-proc-mount-disallowed + labels: + app: nginx-proc-mount + spec: + containers: + - name: nginx + image: nginx + securityContext: + procMount: Unmasked #Default diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/suite.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/suite.yaml new file mode 100644 index 0000000000..282246b5ad --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/suite.yaml @@ -0,0 +1,29 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: proc-mount +tests: +- name: default-proc-mount-required + template: template.yaml + constraint: samples/psp-proc-mount/constraint.yaml + cases: + - name: example-disallowed + object: samples/psp-proc-mount/example_disallowed.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/psp-proc-mount/example_allowed.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-proc-mount/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-proc-mount/update.yaml + assertions: + - violations: no + - name: image-exempt-prefix-match + object: samples/psp-proc-mount/example_allowed_exempt_image.yaml + assertions: + - violations: no diff --git a/artifacthub/library/pod-security-policy/proc-mount/1.1.1/template.yaml b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/template.yaml new file mode 100644 index 0000000000..ed32cb3832 --- /dev/null +++ b/artifacthub/library/pod-security-policy/proc-mount/1.1.1/template.yaml @@ -0,0 +1,185 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spspprocmount + annotations: + metadata.gatekeeper.sh/title: "Proc Mount" + metadata.gatekeeper.sh/version: 1.1.1 + description: >- + Controls the allowed `procMount` types for the container. Corresponds to + the `allowedProcMountTypes` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#allowedprocmounttypes +spec: + crd: + spec: + names: + kind: K8sPSPProcMount + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Controls the allowed `procMount` types for the container. Corresponds to + the `allowedProcMountTypes` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#allowedprocmounttypes + 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 + procMount: + type: string + description: >- + Defines the strategy for the security exposure of certain paths + in `/proc` by the container runtime. Setting to `Default` uses + the runtime defaults, where `Unmasked` bypasses the default + behavior. + enum: + - Default + - Unmasked + 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: allowedProcMount + expression: | + !has(variables.params) ? "default" : + !has(variables.params.procMount) ? "default" : + (variables.params.procMount.lowerAscii() == "default" || variables.params.procMount.lowerAscii() == "unmasked") ? variables.params.procMount.lowerAscii() : "default" + - name: badContainers + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + !(container.image in variables.exemptImages) && + !( + (variables.allowedProcMount == "unmasked") || + (variables.allowedProcMount == "default" && has(container.securityContext) && has(container.securityContext.procMount) && container.securityContext.procMount.lowerAscii() == "default") + ) + ).map(container, "ProcMount type is not allowed, container: " + container.name +". Allowed procMount types: " + variables.allowedProcMount) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + messageExpression: 'variables.badContainers.join("\n")' + - engine: Rego + source: + rego: | + package k8spspprocmount + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.containers.securityContext.procMount field is immutable. + not is_update(input.review) + + c := input_containers[_] + not is_exempt(c) + allowedProcMount := get_allowed_proc_mount(input) + not input_proc_mount_type_allowed(allowedProcMount, c) + msg := sprintf("ProcMount type is not allowed, container: %v. Allowed procMount types: %v", [c.name, allowedProcMount]) + } + + input_proc_mount_type_allowed(allowedProcMount, c) { + allowedProcMount == "default" + lower(c.securityContext.procMount) == "default" + } + input_proc_mount_type_allowed(allowedProcMount, _) { + allowedProcMount == "unmasked" + } + + input_containers[c] { + c := input.review.object.spec.containers[_] + c.securityContext.procMount + } + input_containers[c] { + c := input.review.object.spec.initContainers[_] + c.securityContext.procMount + } + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + c.securityContext.procMount + } + + get_allowed_proc_mount(arg) = out { + not arg.parameters + out = "default" + } + get_allowed_proc_mount(arg) = out { + not arg.parameters.procMount + out = "default" + } + get_allowed_proc_mount(arg) = out { + arg.parameters.procMount + not valid_proc_mount(arg.parameters.procMount) + out = "default" + } + get_allowed_proc_mount(arg) = out { + valid_proc_mount(arg.parameters.procMount) + out = lower(arg.parameters.procMount) + } + + valid_proc_mount(str) { + lower(str) == "default" + } + valid_proc_mount(str) { + lower(str) == "unmasked" + } + 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/proc-mount/samples/psp-proc-mount/constraint.yaml b/library/pod-security-policy/proc-mount/samples/psp-proc-mount/constraint.yaml index 1d7434ac01..79ad221ab3 100644 --- a/library/pod-security-policy/proc-mount/samples/psp-proc-mount/constraint.yaml +++ b/library/pod-security-policy/proc-mount/samples/psp-proc-mount/constraint.yaml @@ -9,3 +9,5 @@ spec: kinds: ["Pod"] parameters: procMount: Default + exemptImages: + - "safeimages.com/*" diff --git a/library/pod-security-policy/proc-mount/samples/psp-proc-mount/example_allowed_exempt_image.yaml b/library/pod-security-policy/proc-mount/samples/psp-proc-mount/example_allowed_exempt_image.yaml new file mode 100644 index 0000000000..c197740a4e --- /dev/null +++ b/library/pod-security-policy/proc-mount/samples/psp-proc-mount/example_allowed_exempt_image.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-proc-mount-exempt-image + labels: + app: nginx-proc-mount +spec: + hostUsers: false + containers: + - name: nginx + image: safeimages.com/nginx + securityContext: + procMount: Unmasked #Default diff --git a/library/pod-security-policy/proc-mount/suite.yaml b/library/pod-security-policy/proc-mount/suite.yaml index 501493e141..282246b5ad 100644 --- a/library/pod-security-policy/proc-mount/suite.yaml +++ b/library/pod-security-policy/proc-mount/suite.yaml @@ -23,3 +23,7 @@ tests: object: samples/psp-proc-mount/update.yaml assertions: - violations: no + - name: image-exempt-prefix-match + object: samples/psp-proc-mount/example_allowed_exempt_image.yaml + assertions: + - violations: no diff --git a/library/pod-security-policy/proc-mount/template.yaml b/library/pod-security-policy/proc-mount/template.yaml index 1007de1076..ed32cb3832 100644 --- a/library/pod-security-policy/proc-mount/template.yaml +++ b/library/pod-security-policy/proc-mount/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spspprocmount annotations: metadata.gatekeeper.sh/title: "Proc Mount" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Controls the allowed `procMount` types for the container. Corresponds to the `allowedProcMountTypes` field in a PodSecurityPolicy. For more @@ -67,9 +67,14 @@ spec: variables.params.exemptImages.filter(image, !image.endsWith("*")) - name: exemptImages expression: | - (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter( + container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists( + exemption, + string(container.image).startsWith(exemption) + ) + ).map(container, container.image) - name: allowedProcMount expression: | !has(variables.params) ? "default" : diff --git a/src/pod-security-policy/proc-mount/constraint.tmpl b/src/pod-security-policy/proc-mount/constraint.tmpl index 0499c535a4..0a43cd8452 100644 --- a/src/pod-security-policy/proc-mount/constraint.tmpl +++ b/src/pod-security-policy/proc-mount/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spspprocmount annotations: metadata.gatekeeper.sh/title: "Proc Mount" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Controls the allowed `procMount` types for the container. Corresponds to the `allowedProcMountTypes` field in a PodSecurityPolicy. For more diff --git a/src/pod-security-policy/proc-mount/src.cel b/src/pod-security-policy/proc-mount/src.cel index b7cef9b983..df43310289 100644 --- a/src/pod-security-policy/proc-mount/src.cel +++ b/src/pod-security-policy/proc-mount/src.cel @@ -15,9 +15,14 @@ variables: variables.params.exemptImages.filter(image, !image.endsWith("*")) - name: exemptImages expression: | - (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter( + container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists( + exemption, + string(container.image).startsWith(exemption) + ) + ).map(container, container.image) - name: allowedProcMount expression: | !has(variables.params) ? "default" : @@ -34,4 +39,4 @@ variables: ).map(container, "ProcMount type is not allowed, container: " + container.name +". Allowed procMount types: " + variables.allowedProcMount) validations: - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' - messageExpression: 'variables.badContainers.join("\n")' \ No newline at end of file + messageExpression: 'variables.badContainers.join("\n")' diff --git a/website/docs/validation/proc-mount.md b/website/docs/validation/proc-mount.md index 17c26d23fb..635e0c57e8 100644 --- a/website/docs/validation/proc-mount.md +++ b/website/docs/validation/proc-mount.md @@ -16,7 +16,7 @@ metadata: name: k8spspprocmount annotations: metadata.gatekeeper.sh/title: "Proc Mount" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Controls the allowed `procMount` types for the container. Corresponds to the `allowedProcMountTypes` field in a PodSecurityPolicy. For more @@ -79,9 +79,14 @@ spec: variables.params.exemptImages.filter(image, !image.endsWith("*")) - name: exemptImages expression: | - (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter( + container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists( + exemption, + string(container.image).startsWith(exemption) + ) + ).map(container, container.image) - name: allowedProcMount expression: | !has(variables.params) ? "default" : @@ -216,6 +221,8 @@ spec: kinds: ["Pod"] parameters: procMount: Default + exemptImages: + - "safeimages.com/*" ``` @@ -307,6 +314,33 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/proc-mount/samples/psp-proc-mount/disallowed_ephemeral.yaml ``` +
+
+image-exempt-prefix-match + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-proc-mount-exempt-image + labels: + app: nginx-proc-mount +spec: + hostUsers: false + containers: + - name: nginx + image: safeimages.com/nginx + securityContext: + procMount: Unmasked #Default + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/proc-mount/samples/psp-proc-mount/example_allowed_exempt_image.yaml +``` +
From 525a0050e47678a5500660d205742d7ed070e8e8 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Fri, 30 Aug 2024 13:32:01 -0700 Subject: [PATCH 14/20] fix(k8spsphostnetworkingports): CEL fixes for hostNetwork variable and (#589) message My updates to the suite.yaml file yielded an expected failure due to an incorrect CEL expression: ``` unexpected number of violations: got 1 violations but want none: got messages [failed expression: (has(request.operation) && request.operation == "UPDATE") || (!has(variables.params.hostNetwork) || !variables.params.hostNetwork ? (has(variables.anyObject.spec.hostNetwork) && !variables.anyObject.spec.hostNetwork) : true)] ``` By contrast, a run of `gator verify -v . --enable-k8s-native-validation=false` yielded a fully passing suite.yaml. This expression was actually failing due to its `messageExpression`, as non-primitive types cannot be combined with strings as in some interpreted languages (like rego). Unfortunately the compiler does not indicate that the messageExpression is the source of the problem. Once the message was fixed, I resolved the incorrect violation expression to fix the bug in the handling of params.hostNetwork. Signed-off-by: juliankatz --- .../1.1.3/artifacthub-pkg.yml | 22 +++ .../1.1.3/kustomization.yaml | 2 + .../block_host_network/constraint.yaml | 2 - .../constraint.yaml | 13 ++ ...llowed_out_of_range_host_network_true.yaml | 0 .../psp-host-network-ports/constraint.yaml | 13 ++ .../disallowed_ephemeral.yaml | 14 ++ .../example_allowed_in_range.yaml | 1 - .../example_allowed_no_ports.yaml | 11 ++ ...e_allowed_no_ports_host_network_false.yaml | 9 + ...le_allowed_no_ports_host_network_true.yaml | 9 + .../psp-host-network-ports/update.yaml | 19 ++ .../host-network-ports/1.1.3/suite.yaml | 62 +++++++ .../host-network-ports/1.1.3/template.yaml | 165 ++++++++++++++++++ .../block_host_network/constraint.yaml | 11 ++ .../constraint.yaml | 13 ++ ...llowed_out_of_range_host_network_true.yaml | 14 ++ .../example_allowed_in_range.yaml | 13 ++ ...e_allowed_no_ports_host_network_false.yaml | 9 + ...le_allowed_no_ports_host_network_true.yaml | 9 + .../host-network-ports/suite.yaml | 34 ++-- .../host-network-ports/template.yaml | 20 ++- .../host-network-ports/constraint.tmpl | 2 +- .../host-network-ports/src.cel | 18 +- website/docs/validation/host-network-ports.md | 126 ++++++++++--- 25 files changed, 561 insertions(+), 50 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/kustomization.yaml rename library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint_block_host_network.yaml => artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/block_host_network/constraint.yaml (87%) create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/port_range_block_host_network/constraint.yaml rename library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_disallowed.yaml => artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml (100%) create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/disallowed_ephemeral.yaml rename library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed.yaml => artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_in_range.yaml (91%) create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/update.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.3/template.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/block_host_network/constraint.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_in_range.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/artifacthub-pkg.yml new file mode 100644 index 0000000000..878f809ba9 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.3 +name: k8spsphostnetworkingports +displayName: Host Networking Ports +createdAt: "2024-08-29T21:28:18Z" +description: Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and `hostPorts` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +digest: 751752950daeb4002a10cad6cbeba6a4afe03b98605f32885ae3fe0179eaff67 +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/host-network-ports +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Host Networking Ports + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and `hostPorts` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/kustomization.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint_block_host_network.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/block_host_network/constraint.yaml similarity index 87% rename from library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint_block_host_network.yaml rename to artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/block_host_network/constraint.yaml index 7ef87dbb39..b5f2e9f44a 100644 --- a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint_block_host_network.yaml +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/block_host_network/constraint.yaml @@ -9,5 +9,3 @@ spec: kinds: ["Pod"] parameters: hostNetwork: false - exemptImages: - - "nginx" \ No newline at end of file diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/port_range_block_host_network/constraint.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/port_range_block_host_network/constraint.yaml new file mode 100644 index 0000000000..46e16454c7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/port_range_block_host_network/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network-ports +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: false + min: 80 + max: 9000 diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_disallowed.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml similarity index 100% rename from library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_disallowed.yaml rename to artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/constraint.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/constraint.yaml new file mode 100644 index 0000000000..aba7c24e76 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network-ports +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: true + min: 80 + max: 9000 \ No newline at end of file diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..7a4fa31144 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/disallowed_ephemeral.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + ephemeralContainers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_in_range.yaml similarity index 91% rename from library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed.yaml rename to artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_in_range.yaml index 08b321fe58..2b4f7c9266 100644 --- a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed.yaml +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_in_range.yaml @@ -5,7 +5,6 @@ metadata: labels: app: nginx-host-networking-ports spec: - hostNetwork: false containers: - name: nginx image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports.yaml new file mode 100644 index 0000000000..e009decf90 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml new file mode 100644 index 0000000000..8c0b0ef577 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-false +spec: + hostNetwork: false + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml new file mode 100644 index 0000000000..91cd7f4cd7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-true +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/update.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/update.yaml new file mode 100644 index 0000000000..231096430e --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/samples/psp-host-network-ports/update.yaml @@ -0,0 +1,19 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports + spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/suite.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/suite.yaml new file mode 100644 index 0000000000..44bbab1bea --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/suite.yaml @@ -0,0 +1,62 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: host-network-ports +tests: +- name: port-range-with-host-network-allowed + template: template.yaml + constraint: samples/psp-host-network-ports/constraint.yaml + cases: + - name: out-of-range + object: samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/psp-host-network-ports/example_allowed_in_range.yaml + assertions: + - violations: no + - name: out-of-range-ephemeral + object: samples/psp-host-network-ports/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-host-network-ports/update.yaml + assertions: + - violations: no + - name: no-ports-specified + object: samples/psp-host-network-ports/example_allowed_no_ports.yaml + assertions: + - violations: no +- name: host-network-forbidden + template: template.yaml + constraint: samples/block_host_network/constraint.yaml + cases: + - name: hostnetwork-true + object: samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml + assertions: + - violations: yes + - name: hostnetwork-false + object: samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml + assertions: + - violations: no +- name: port-range-with-host-network-forbidden + template: template.yaml + constraint: samples/port_range_block_host_network/constraint.yaml + cases: + - name: out-of-range-and-host-network-true + object: samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml + assertions: + - violations: yes + - name: in-range-host-network-false + object: samples/psp-host-network-ports/example_allowed_in_range.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-host-network-ports/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-host-network-ports/update.yaml + assertions: + - violations: no + diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/template.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/template.yaml new file mode 100644 index 0000000000..036a3e045a --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.3/template.yaml @@ -0,0 +1,165 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spsphostnetworkingports + annotations: + metadata.gatekeeper.sh/title: "Host Networking Ports" + metadata.gatekeeper.sh/version: 1.1.3 + description: >- + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific + ports must be specified. Corresponds to the `hostNetwork` and + `hostPorts` fields in a PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +spec: + crd: + spec: + names: + kind: K8sPSPHostNetworkingPorts + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific + ports must be specified. Corresponds to the `hostNetwork` and + `hostPorts` fields in a PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces + 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 + hostNetwork: + description: "Determines if the policy allows the use of HostNetwork in the pod spec." + type: boolean + min: + description: "The start of the allowed port range, inclusive." + type: integer + max: + description: "The end of the allowed port range, inclusive." + type: integer + 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))) + - name: badContainers + expression: | + (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, + !(container.image in variables.exemptImages) && has(container.ports) && + ( + (container.ports.all(port, has(port.hostPort) && has(variables.params.min) && port.hostPort < variables.params.min)) || + (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) + ) + ) + - name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" + - name: hostNetworkAllowed + expression: has(variables.params.hostNetwork) && variables.params.hostNetwork + - name: hostNetworkEnabled + expression: has(variables.anyObject.spec.hostNetwork) && variables.anyObject.spec.hostNetwork + - name: hostNetworkViolation + expression: variables.hostNetworkEnabled && !variables.hostNetworkAllowed + validations: + - expression: 'variables.isUpdate || size(variables.badContainers) == 0' + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' + - expression: variables.isUpdate || !variables.hostNetworkViolation + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' + - engine: Rego + source: + rego: | + package k8spsphostnetworkingports + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.hostNetwork field is immutable. + not is_update(input.review) + + input_share_hostnetwork(input.review.object) + msg := sprintf("The specified hostNetwork and hostPort are not allowed, pod: %v. Allowed values: %v", [input.review.object.metadata.name, input.parameters]) + } + + input_share_hostnetwork(o) { + not input.parameters.hostNetwork + o.spec.hostNetwork + } + + input_share_hostnetwork(_) { + hostPort := input_containers[_].ports[_].hostPort + hostPort < input.parameters.min + } + + input_share_hostnetwork(_) { + hostPort := input_containers[_].ports[_].hostPort + hostPort > input.parameters.max + } + + input_containers[c] { + c := input.review.object.spec.containers[_] + not is_exempt(c) + } + + input_containers[c] { + c := input.review.object.spec.initContainers[_] + not is_exempt(c) + } + + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + not is_exempt(c) + } + 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/host-network-ports/samples/block_host_network/constraint.yaml b/library/pod-security-policy/host-network-ports/samples/block_host_network/constraint.yaml new file mode 100644 index 0000000000..b5f2e9f44a --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/block_host_network/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: false diff --git a/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml new file mode 100644 index 0000000000..46e16454c7 --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network-ports +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: false + min: 80 + max: 9000 diff --git a/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml new file mode 100644 index 0000000000..9a496cd606 --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_in_range.yaml b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_in_range.yaml new file mode 100644 index 0000000000..2b4f7c9266 --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_in_range.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-allowed + labels: + app: nginx-host-networking-ports +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9000 + hostPort: 80 diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml new file mode 100644 index 0000000000..8c0b0ef577 --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-false +spec: + hostNetwork: false + containers: + - name: nginx + image: nginx diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml new file mode 100644 index 0000000000..91cd7f4cd7 --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-true +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx diff --git a/library/pod-security-policy/host-network-ports/suite.yaml b/library/pod-security-policy/host-network-ports/suite.yaml index b0c7f7816c..44bbab1bea 100644 --- a/library/pod-security-policy/host-network-ports/suite.yaml +++ b/library/pod-security-policy/host-network-ports/suite.yaml @@ -3,19 +3,19 @@ apiVersion: test.gatekeeper.sh/v1alpha1 metadata: name: host-network-ports tests: -- name: use-of-host-networking-ports-blocked +- name: port-range-with-host-network-allowed template: template.yaml constraint: samples/psp-host-network-ports/constraint.yaml cases: - - name: example-disallowed - object: samples/psp-host-network-ports/example_disallowed.yaml + - name: out-of-range + object: samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml assertions: - violations: yes - name: example-allowed - object: samples/psp-host-network-ports/example_allowed.yaml + object: samples/psp-host-network-ports/example_allowed_in_range.yaml assertions: - violations: no - - name: disallowed-ephemeral + - name: out-of-range-ephemeral object: samples/psp-host-network-ports/disallowed_ephemeral.yaml assertions: - violations: yes @@ -27,16 +27,28 @@ tests: object: samples/psp-host-network-ports/example_allowed_no_ports.yaml assertions: - violations: no -- name: use-of-host-network-blocked +- name: host-network-forbidden template: template.yaml - constraint: samples/psp-host-network-ports/constraint_block_host_network.yaml + constraint: samples/block_host_network/constraint.yaml cases: - - name: example-disallowed - object: samples/psp-host-network-ports/example_disallowed.yaml + - name: hostnetwork-true + object: samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml assertions: - violations: yes - - name: example-allowed - object: samples/psp-host-network-ports/example_allowed.yaml + - name: hostnetwork-false + object: samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml + assertions: + - violations: no +- name: port-range-with-host-network-forbidden + template: template.yaml + constraint: samples/port_range_block_host_network/constraint.yaml + cases: + - name: out-of-range-and-host-network-true + object: samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml + assertions: + - violations: yes + - name: in-range-host-network-false + object: samples/psp-host-network-ports/example_allowed_in_range.yaml assertions: - violations: no - name: disallowed-ephemeral diff --git a/library/pod-security-policy/host-network-ports/template.yaml b/library/pod-security-policy/host-network-ports/template.yaml index 218cb36e7d..036a3e045a 100644 --- a/library/pod-security-policy/host-network-ports/template.yaml +++ b/library/pod-security-policy/host-network-ports/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.2 + metadata.gatekeeper.sh/version: 1.1.3 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and @@ -78,13 +78,19 @@ spec: (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) ) ) + - name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" + - name: hostNetworkAllowed + expression: has(variables.params.hostNetwork) && variables.params.hostNetwork + - name: hostNetworkEnabled + expression: has(variables.anyObject.spec.hostNetwork) && variables.anyObject.spec.hostNetwork + - name: hostNetworkViolation + expression: variables.hostNetworkEnabled && !variables.hostNetworkAllowed validations: - - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' - messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' - - expression: | - (has(request.operation) && request.operation == "UPDATE") || - (!has(variables.params.hostNetwork) || !variables.params.hostNetwork ? (has(variables.anyObject.spec.hostNetwork) && !variables.anyObject.spec.hostNetwork) : true) - messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' + - expression: 'variables.isUpdate || size(variables.badContainers) == 0' + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' + - expression: variables.isUpdate || !variables.hostNetworkViolation + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' - engine: Rego source: rego: | diff --git a/src/pod-security-policy/host-network-ports/constraint.tmpl b/src/pod-security-policy/host-network-ports/constraint.tmpl index 9f6e90d281..d5f7146821 100644 --- a/src/pod-security-policy/host-network-ports/constraint.tmpl +++ b/src/pod-security-policy/host-network-ports/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.2 + metadata.gatekeeper.sh/version: 1.1.3 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and diff --git a/src/pod-security-policy/host-network-ports/src.cel b/src/pod-security-policy/host-network-ports/src.cel index 1bd9b333c1..8d0fe507ec 100644 --- a/src/pod-security-policy/host-network-ports/src.cel +++ b/src/pod-security-policy/host-network-ports/src.cel @@ -27,10 +27,16 @@ variables: (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) ) ) +- name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" +- name: hostNetworkAllowed + expression: has(variables.params.hostNetwork) && variables.params.hostNetwork +- name: hostNetworkEnabled + expression: has(variables.anyObject.spec.hostNetwork) && variables.anyObject.spec.hostNetwork +- name: hostNetworkViolation + expression: variables.hostNetworkEnabled && !variables.hostNetworkAllowed validations: -- expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' - messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' -- expression: | - (has(request.operation) && request.operation == "UPDATE") || - (!has(variables.params.hostNetwork) || !variables.params.hostNetwork ? (has(variables.anyObject.spec.hostNetwork) && !variables.anyObject.spec.hostNetwork) : true) - messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' +- expression: 'variables.isUpdate || size(variables.badContainers) == 0' + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' +- expression: variables.isUpdate || !variables.hostNetworkViolation + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' diff --git a/website/docs/validation/host-network-ports.md b/website/docs/validation/host-network-ports.md index 17c2b6b6ba..549b373803 100644 --- a/website/docs/validation/host-network-ports.md +++ b/website/docs/validation/host-network-ports.md @@ -16,7 +16,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.2 + metadata.gatekeeper.sh/version: 1.1.3 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and @@ -90,13 +90,19 @@ spec: (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) ) ) + - name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" + - name: hostNetworkAllowed + expression: has(variables.params.hostNetwork) && variables.params.hostNetwork + - name: hostNetworkEnabled + expression: has(variables.anyObject.spec.hostNetwork) && variables.anyObject.spec.hostNetwork + - name: hostNetworkViolation + expression: variables.hostNetworkEnabled && !variables.hostNetworkAllowed validations: - - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' - messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' - - expression: | - (has(request.operation) && request.operation == "UPDATE") || - (!has(variables.params.hostNetwork) || !variables.params.hostNetwork ? (has(variables.anyObject.spec.hostNetwork) && !variables.anyObject.spec.hostNetwork) : true) - messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name + ". Allowed values: " + variables.params' + - expression: 'variables.isUpdate || size(variables.badContainers) == 0' + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' + - expression: variables.isUpdate || !variables.hostNetworkViolation + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' - engine: Rego source: rego: | @@ -178,7 +184,7 @@ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper- ``` ## Examples
-use-of-host-networking-ports-blocked +port-range-with-host-network-allowed
constraint @@ -208,7 +214,7 @@ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-
-example-disallowed +out-of-range ```yaml apiVersion: v1 @@ -231,7 +237,7 @@ spec: Usage ```shell -kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_disallowed.yaml +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml ```
@@ -246,7 +252,6 @@ metadata: labels: app: nginx-host-networking-ports spec: - hostNetwork: false containers: - name: nginx image: nginx @@ -259,12 +264,12 @@ spec: Usage ```shell -kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed.yaml +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_in_range.yaml ```
-disallowed-ephemeral +out-of-range-ephemeral ```yaml apiVersion: v1 @@ -319,7 +324,7 @@ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-
-use-of-host-network-blocked +host-network-forbidden
constraint @@ -336,20 +341,98 @@ spec: kinds: ["Pod"] parameters: hostNetwork: false - exemptImages: - - "nginx" + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/block_host_network/constraint.yaml +``` + +
+ +
+hostnetwork-true + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-true +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml +``` + +
+
+hostnetwork-false + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-false +spec: + hostNetwork: false + containers: + - name: nginx + image: nginx + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml +``` + +
+ + +
+port-range-with-host-network-forbidden + +
+constraint + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network-ports +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: false + min: 80 + max: 9000 + ``` Usage ```shell -kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint_block_host_network.yaml +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml ```
-example-disallowed +out-of-range-and-host-network-true ```yaml apiVersion: v1 @@ -372,12 +455,12 @@ spec: Usage ```shell -kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_disallowed.yaml +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml ```
-example-allowed +in-range-host-network-false ```yaml apiVersion: v1 @@ -387,7 +470,6 @@ metadata: labels: app: nginx-host-networking-ports spec: - hostNetwork: false containers: - name: nginx image: nginx @@ -400,7 +482,7 @@ spec: Usage ```shell -kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed.yaml +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_in_range.yaml ```
From 033906e30c07168907e3b4fec5848e1fa4178ff5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:04:37 -0700 Subject: [PATCH 15/20] chore: bump the all group with 2 updates (#594) Bumps the all group with 2 updates: [github/codeql-action](https://github.com/github/codeql-action) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `github/codeql-action` from 3.26.5 to 3.26.6 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2c779ab0d087cd7fe7b826087247c2c81f27bfa6...4dd16135b69a43b6c8efb853346f8437d92d3c93) Updates `actions/upload-artifact` from 4.3.6 to 4.4.0 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/834a144ee995460fba8ed112a2fc961b36a5ec5a...50769540e7f4bd5e21e526ee35c689e35e0d6874) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 4 ++-- .github/workflows/workflow.yaml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b0267abcec..83584c2d25 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index fee0d76379..f06e457055 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -63,7 +63,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: SARIF file path: results.sarif @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: results.sarif diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 1af1df56fd..dd8e93ebc3 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -99,7 +99,7 @@ jobs: kubectl logs -n gatekeeper-system -l control-plane=audit-controller --tail=-1 > logs-audit.json - name: Upload artifacts - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 if: ${{ always() }} with: name: logs-int-test-${{ matrix.gatekeeper }}-${{ matrix.engine }} From 8b37aeed632c1ed6f6c1d781bd0795dcf9a60598 Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Tue, 3 Sep 2024 18:51:34 -0700 Subject: [PATCH 16/20] fix(k8spsphostnetworkingports): exemptImages CEL bug (#590) I recently found (#584) that some K8sNativeValidation implementations of certain templates that iterate over and exempt containers by image had a bug preventing the exemption logic from working. I've fixed that bug here by mapping from container struct to container.image string. I've also added a suite test to verify this. That case fails without the change to the CEL logic. Signed-off-by: juliankatz --- .../1.1.4/artifacthub-pkg.yml | 22 +++ .../1.1.4/kustomization.yaml | 2 + .../block_host_network/constraint.yaml | 11 ++ .../constraint.yaml | 15 ++ ...empted_container_host_network_enabled.yaml | 14 ++ ...llowed_out_of_range_host_network_true.yaml | 14 ++ .../psp-host-network-ports/constraint.yaml | 15 ++ .../disallowed_ephemeral.yaml | 14 ++ .../example_allowed_in_range.yaml | 13 ++ .../example_allowed_no_ports.yaml | 11 ++ ...e_allowed_no_ports_host_network_false.yaml | 9 + ...le_allowed_no_ports_host_network_true.yaml | 9 + ...example_allowed_out_of_range_exempted.yaml | 14 ++ .../psp-host-network-ports/update.yaml | 19 ++ .../host-network-ports/1.1.4/suite.yaml | 69 ++++++++ .../host-network-ports/1.1.4/template.yaml | 166 ++++++++++++++++++ .../constraint.yaml | 2 + ...empted_container_host_network_enabled.yaml | 14 ++ .../psp-host-network-ports/constraint.yaml | 4 +- ...example_allowed_out_of_range_exempted.yaml | 14 ++ .../host-network-ports/suite.yaml | 9 +- .../host-network-ports/template.yaml | 5 +- .../host-network-ports/constraint.tmpl | 2 +- .../host-network-ports/src.cel | 3 +- website/docs/validation/host-network-ports.md | 66 ++++++- 25 files changed, 528 insertions(+), 8 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/block_host_network/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_in_range.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/update.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/host-network-ports/1.1.4/template.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml create mode 100644 library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/artifacthub-pkg.yml new file mode 100644 index 0000000000..24366190fb --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.4 +name: k8spsphostnetworkingports +displayName: Host Networking Ports +createdAt: "2024-08-30T22:03:40Z" +description: Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and `hostPorts` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +digest: 5e295f3ee2762996e89f926faae128ca3ae86166aac8fb9e518433ba1300deec +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/host-network-ports +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Host Networking Ports + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and `hostPorts` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/kustomization.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/block_host_network/constraint.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/block_host_network/constraint.yaml new file mode 100644 index 0000000000..b5f2e9f44a --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/block_host_network/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: false diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/constraint.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/constraint.yaml new file mode 100644 index 0000000000..c315f9e237 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/constraint.yaml @@ -0,0 +1,15 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network-ports +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: false + min: 80 + max: 9000 + exemptImages: + - "safeimages.com/*" diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml new file mode 100644 index 0000000000..0056f9b1d3 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-hn-ok-bad-port + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: safeimages.com/nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml new file mode 100644 index 0000000000..9a496cd606 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/constraint.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/constraint.yaml new file mode 100644 index 0000000000..b6176404c8 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/constraint.yaml @@ -0,0 +1,15 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostNetworkingPorts +metadata: + name: psp-host-network-ports +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + hostNetwork: true + min: 80 + max: 9000 + exemptImages: + - "safeimages.com/*" diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..7a4fa31144 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/disallowed_ephemeral.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + ephemeralContainers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_in_range.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_in_range.yaml new file mode 100644 index 0000000000..2b4f7c9266 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_in_range.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-allowed + labels: + app: nginx-host-networking-ports +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9000 + hostPort: 80 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports.yaml new file mode 100644 index 0000000000..e009decf90 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml new file mode 100644 index 0000000000..8c0b0ef577 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-false +spec: + hostNetwork: false + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml new file mode 100644 index 0000000000..91cd7f4cd7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-network-true +spec: + hostNetwork: true + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml new file mode 100644 index 0000000000..e4d013447e --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-exempted + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: safeimages.com/nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/update.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/update.yaml new file mode 100644 index 0000000000..231096430e --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/samples/psp-host-network-ports/update.yaml @@ -0,0 +1,19 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: nginx-host-networking-ports-disallowed + labels: + app: nginx-host-networking-ports + spec: + hostNetwork: true + containers: + - name: nginx + image: nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/suite.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/suite.yaml new file mode 100644 index 0000000000..8879f7fc99 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/suite.yaml @@ -0,0 +1,69 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: host-network-ports +tests: +- name: port-range-with-host-network-allowed + template: template.yaml + constraint: samples/psp-host-network-ports/constraint.yaml + cases: + - name: out-of-range + object: samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/psp-host-network-ports/example_allowed_in_range.yaml + assertions: + - violations: no + - name: out-of-range-ephemeral + object: samples/psp-host-network-ports/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-host-network-ports/update.yaml + assertions: + - violations: no + - name: no-ports-specified + object: samples/psp-host-network-ports/example_allowed_no_ports.yaml + assertions: + - violations: no + - name: port-violation-exempted + object: samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml + assertions: + - violations: no +- name: host-network-forbidden + template: template.yaml + constraint: samples/block_host_network/constraint.yaml + cases: + - name: hostnetwork-true + object: samples/psp-host-network-ports/example_allowed_no_ports_host_network_true.yaml + assertions: + - violations: yes + - name: hostnetwork-false + object: samples/psp-host-network-ports/example_allowed_no_ports_host_network_false.yaml + assertions: + - violations: no +- name: port-range-with-host-network-forbidden + template: template.yaml + constraint: samples/port_range_block_host_network/constraint.yaml + cases: + - name: out-of-range-and-host-network-true + object: samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml + assertions: + - violations: yes + - name: exempted-image-still-violates-on-hostnetwork + object: samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml + assertions: + - violations: yes + - name: in-range-host-network-false + object: samples/psp-host-network-ports/example_allowed_in_range.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-host-network-ports/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-host-network-ports/update.yaml + assertions: + - violations: no diff --git a/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/template.yaml b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/template.yaml new file mode 100644 index 0000000000..c310ffcbbc --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-network-ports/1.1.4/template.yaml @@ -0,0 +1,166 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spsphostnetworkingports + annotations: + metadata.gatekeeper.sh/title: "Host Networking Ports" + metadata.gatekeeper.sh/version: 1.1.4 + description: >- + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific + ports must be specified. Corresponds to the `hostNetwork` and + `hostPorts` fields in a PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces +spec: + crd: + spec: + names: + kind: K8sPSPHostNetworkingPorts + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific + ports must be specified. Corresponds to the `hostNetwork` and + `hostPorts` fields in a PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces + 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 + hostNetwork: + description: "Determines if the policy allows the use of HostNetwork in the pod spec." + type: boolean + min: + description: "The start of the allowed port range, inclusive." + type: integer + max: + description: "The end of the allowed port range, inclusive." + type: integer + 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.ports) && + ( + (container.ports.all(port, has(port.hostPort) && has(variables.params.min) && port.hostPort < variables.params.min)) || + (container.ports.all(port, has(port.hostPort) && has(variables.params.max) && port.hostPort > variables.params.max)) + ) + ) + - name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" + - name: hostNetworkAllowed + expression: has(variables.params.hostNetwork) && variables.params.hostNetwork + - name: hostNetworkEnabled + expression: has(variables.anyObject.spec.hostNetwork) && variables.anyObject.spec.hostNetwork + - name: hostNetworkViolation + expression: variables.hostNetworkEnabled && !variables.hostNetworkAllowed + validations: + - expression: 'variables.isUpdate || size(variables.badContainers) == 0' + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' + - expression: variables.isUpdate || !variables.hostNetworkViolation + messageExpression: '"The specified hostNetwork and hostPort are not allowed, pod: " + variables.anyObject.metadata.name' + - engine: Rego + source: + rego: | + package k8spsphostnetworkingports + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.hostNetwork field is immutable. + not is_update(input.review) + + input_share_hostnetwork(input.review.object) + msg := sprintf("The specified hostNetwork and hostPort are not allowed, pod: %v. Allowed values: %v", [input.review.object.metadata.name, input.parameters]) + } + + input_share_hostnetwork(o) { + not input.parameters.hostNetwork + o.spec.hostNetwork + } + + input_share_hostnetwork(_) { + hostPort := input_containers[_].ports[_].hostPort + hostPort < input.parameters.min + } + + input_share_hostnetwork(_) { + hostPort := input_containers[_].ports[_].hostPort + hostPort > input.parameters.max + } + + input_containers[c] { + c := input.review.object.spec.containers[_] + not is_exempt(c) + } + + input_containers[c] { + c := input.review.object.spec.initContainers[_] + not is_exempt(c) + } + + input_containers[c] { + c := input.review.object.spec.ephemeralContainers[_] + not is_exempt(c) + } + 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/host-network-ports/samples/port_range_block_host_network/constraint.yaml b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml index 46e16454c7..c315f9e237 100644 --- a/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml +++ b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/constraint.yaml @@ -11,3 +11,5 @@ spec: hostNetwork: false min: 80 max: 9000 + exemptImages: + - "safeimages.com/*" diff --git a/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml new file mode 100644 index 0000000000..0056f9b1d3 --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-hn-ok-bad-port + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: safeimages.com/nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint.yaml b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint.yaml index aba7c24e76..b6176404c8 100644 --- a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint.yaml +++ b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/constraint.yaml @@ -10,4 +10,6 @@ spec: parameters: hostNetwork: true min: 80 - max: 9000 \ No newline at end of file + max: 9000 + exemptImages: + - "safeimages.com/*" diff --git a/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml new file mode 100644 index 0000000000..e4d013447e --- /dev/null +++ b/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-exempted + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: safeimages.com/nginx + ports: + - containerPort: 9001 + hostPort: 9001 diff --git a/library/pod-security-policy/host-network-ports/suite.yaml b/library/pod-security-policy/host-network-ports/suite.yaml index 44bbab1bea..8879f7fc99 100644 --- a/library/pod-security-policy/host-network-ports/suite.yaml +++ b/library/pod-security-policy/host-network-ports/suite.yaml @@ -27,6 +27,10 @@ tests: object: samples/psp-host-network-ports/example_allowed_no_ports.yaml assertions: - violations: no + - name: port-violation-exempted + object: samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml + assertions: + - violations: no - name: host-network-forbidden template: template.yaml constraint: samples/block_host_network/constraint.yaml @@ -47,6 +51,10 @@ tests: object: samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml assertions: - violations: yes + - name: exempted-image-still-violates-on-hostnetwork + object: samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml + assertions: + - violations: yes - name: in-range-host-network-false object: samples/psp-host-network-ports/example_allowed_in_range.yaml assertions: @@ -59,4 +67,3 @@ tests: object: samples/psp-host-network-ports/update.yaml assertions: - violations: no - diff --git a/library/pod-security-policy/host-network-ports/template.yaml b/library/pod-security-policy/host-network-ports/template.yaml index 036a3e045a..c310ffcbbc 100644 --- a/library/pod-security-policy/host-network-ports/template.yaml +++ b/library/pod-security-policy/host-network-ports/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.3 + metadata.gatekeeper.sh/version: 1.1.4 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and @@ -68,7 +68,8 @@ spec: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) + ).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, diff --git a/src/pod-security-policy/host-network-ports/constraint.tmpl b/src/pod-security-policy/host-network-ports/constraint.tmpl index d5f7146821..51637ed2b2 100644 --- a/src/pod-security-policy/host-network-ports/constraint.tmpl +++ b/src/pod-security-policy/host-network-ports/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.3 + metadata.gatekeeper.sh/version: 1.1.4 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and diff --git a/src/pod-security-policy/host-network-ports/src.cel b/src/pod-security-policy/host-network-ports/src.cel index 8d0fe507ec..a78f549852 100644 --- a/src/pod-security-policy/host-network-ports/src.cel +++ b/src/pod-security-policy/host-network-ports/src.cel @@ -17,7 +17,8 @@ variables: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) + ).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, diff --git a/website/docs/validation/host-network-ports.md b/website/docs/validation/host-network-ports.md index 549b373803..8dd053f9b7 100644 --- a/website/docs/validation/host-network-ports.md +++ b/website/docs/validation/host-network-ports.md @@ -16,7 +16,7 @@ metadata: name: k8spsphostnetworkingports annotations: metadata.gatekeeper.sh/title: "Host Networking Ports" - metadata.gatekeeper.sh/version: 1.1.3 + metadata.gatekeeper.sh/version: 1.1.4 description: >- Controls usage of host network namespace by pod containers. HostNetwork verification happens without exception for exemptImages. Specific ports must be specified. Corresponds to the `hostNetwork` and @@ -80,7 +80,8 @@ spec: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)) + ).map(container, container.image) - name: badContainers expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, @@ -203,6 +204,9 @@ spec: hostNetwork: true min: 80 max: 9000 + exemptImages: + - "safeimages.com/*" + ``` Usage @@ -320,6 +324,34 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_no_ports.yaml ``` +
+
+port-violation-exempted + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-ports-exempted + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: safeimages.com/nginx + ports: + - containerPort: 9001 + hostPort: 9001 + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/psp-host-network-ports/example_allowed_out_of_range_exempted.yaml +``` +
@@ -420,6 +452,8 @@ spec: hostNetwork: false min: 80 max: 9000 + exemptImages: + - "safeimages.com/*" ``` @@ -458,6 +492,34 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_out_of_range_host_network_true.yaml ``` + +
+exempted-image-still-violates-on-hostnetwork + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-networking-hn-ok-bad-port + labels: + app: nginx-host-networking-ports +spec: + hostNetwork: true + containers: + - name: nginx + image: safeimages.com/nginx + ports: + - containerPort: 9001 + hostPort: 9001 + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/host-network-ports/samples/port_range_block_host_network/example_disallowed_exempted_container_host_network_enabled.yaml +``` +
in-range-host-network-false From cd8f2ecab7402e9635a50c29476a7822324c599a Mon Sep 17 00:00:00 2001 From: Julian Katz Date: Tue, 3 Sep 2024 18:52:28 -0700 Subject: [PATCH 17/20] fix(k8spspprivilegedcontainer): exemptImages CEL bug (#591) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I recently found (#584) that some K8sNativeValidation implementations of certain templates that iterate over and exempt containers by image had a bug preventing the exemption logic from working. I've fixed that bug here by mapping from container struct to container.image string. I've also added a suite test to verify this. That case fails without the change to the CEL logic. Signed-off-by: juliankatz Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- .../1.1.1/artifacthub-pkg.yml | 22 +++ .../1.1.1/kustomization.yaml | 2 + .../psp-privileged-container/constraint.yaml | 13 ++ .../disallowed_ephemeral.yaml | 12 ++ .../example_allowed.yaml | 12 ++ .../example_allowed_exempt.yaml | 12 ++ .../example_disallowed.yaml | 12 ++ .../psp-privileged-container/update.yaml | 17 +++ .../privileged-containers/1.1.1/suite.yaml | 29 ++++ .../privileged-containers/1.1.1/template.yaml | 129 ++++++++++++++++++ .../psp-privileged-container/constraint.yaml | 3 + .../example_allowed_exempt.yaml | 12 ++ .../privileged-containers/suite.yaml | 4 + .../privileged-containers/template.yaml | 9 +- .../privileged-containers/constraint.tmpl | 2 +- .../privileged-containers/src.cel | 9 +- .../docs/validation/privileged-containers.md | 38 +++++- 17 files changed, 327 insertions(+), 10 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed_exempt.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/update.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/privileged-containers/1.1.1/template.yaml create mode 100644 library/pod-security-policy/privileged-containers/samples/psp-privileged-container/example_allowed_exempt.yaml diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/artifacthub-pkg.yml new file mode 100644 index 0000000000..5f2dffd891 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.1 +name: k8spspprivilegedcontainer +displayName: Privileged Container +createdAt: "2024-08-30T22:14:08Z" +description: Controls the ability of any container to enable privileged mode. Corresponds to the `privileged` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged +digest: 5d9b2b840bb1f530d3e66cb44d4ab170e7d4b7895d722a51999134a032b61c6f +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/privileged-containers +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Privileged Container + Controls the ability of any container to enable privileged mode. Corresponds to the `privileged` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/kustomization.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/constraint.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/constraint.yaml new file mode 100644 index 0000000000..bf2fe519d6 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPPrivilegedContainer +metadata: + name: psp-privileged-container +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + excludedNamespaces: ["kube-system"] + parameters: + exemptImages: + - "safeimages.com/*" diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..e8c8b99453 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/disallowed_ephemeral.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privileged-disallowed + labels: + app: nginx-privileged +spec: + ephemeralContainers: + - name: nginx + image: nginx + securityContext: + privileged: true diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed.yaml new file mode 100644 index 0000000000..bb65a2c0e7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privileged-allowed + labels: + app: nginx-privileged +spec: + containers: + - name: nginx + image: nginx + securityContext: + privileged: false diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed_exempt.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed_exempt.yaml new file mode 100644 index 0000000000..782a6036a1 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_allowed_exempt.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privileged-allowed-exempt + labels: + app: nginx-privileged +spec: + containers: + - name: nginx + image: safeimages.com/nginx + securityContext: + privileged: true diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_disallowed.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_disallowed.yaml new file mode 100644 index 0000000000..936a24f8ed --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/example_disallowed.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privileged-disallowed + labels: + app: nginx-privileged +spec: + containers: + - name: nginx + image: nginx + securityContext: + privileged: true diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/update.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-container/update.yaml new file mode 100644 index 0000000000..08f36044ca --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/samples/psp-privileged-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-privileged-disallowed + labels: + app: nginx-privileged + spec: + containers: + - name: nginx + image: nginx + securityContext: + privileged: true diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/suite.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/suite.yaml new file mode 100644 index 0000000000..3dd4ff8639 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/suite.yaml @@ -0,0 +1,29 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: privileged-containers +tests: +- name: privileged-containers-disallowed + template: template.yaml + constraint: samples/psp-privileged-container/constraint.yaml + cases: + - name: example-disallowed + object: samples/psp-privileged-container/example_disallowed.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/psp-privileged-container/example_allowed.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-privileged-container/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-privileged-container/update.yaml + assertions: + - violations: no + - name: exempted-image + object: samples/psp-privileged-container/example_allowed_exempt.yaml + assertions: + - violations: no diff --git a/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/template.yaml b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/template.yaml new file mode 100644 index 0000000000..c5a09674c6 --- /dev/null +++ b/artifacthub/library/pod-security-policy/privileged-containers/1.1.1/template.yaml @@ -0,0 +1,129 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spspprivilegedcontainer + annotations: + metadata.gatekeeper.sh/title: "Privileged Container" + metadata.gatekeeper.sh/version: 1.1.1 + description: >- + Controls the ability of any container to enable privileged mode. + Corresponds to the `privileged` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged +spec: + crd: + spec: + names: + kind: K8sPSPPrivilegedContainer + validation: + openAPIV3Schema: + type: object + description: >- + Controls the ability of any container to enable privileged mode. + Corresponds to the `privileged` field in a PodSecurityPolicy. For more + information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged + 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.privileged) && container.securityContext.privileged == true) + ).map(container, "Privileged container is not allowed: " + container.name +", securityContext: " + container.securityContext) + - name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" + validations: + - expression: variables.isUpdate || size(variables.badContainers) == 0 + messageExpression: 'variables.badContainers.join("\n")' + - engine: Rego + source: + rego: | + package k8spspprivileged + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + # spec.containers.privileged field is immutable. + not is_update(input.review) + + c := input_containers[_] + not is_exempt(c) + c.securityContext.privileged + msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext]) + } + + 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[_] + } + 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/privileged-containers/samples/psp-privileged-container/constraint.yaml b/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/constraint.yaml index b246b244a5..bf2fe519d6 100644 --- a/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/constraint.yaml +++ b/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/constraint.yaml @@ -8,3 +8,6 @@ spec: - apiGroups: [""] kinds: ["Pod"] excludedNamespaces: ["kube-system"] + parameters: + exemptImages: + - "safeimages.com/*" diff --git a/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/example_allowed_exempt.yaml b/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/example_allowed_exempt.yaml new file mode 100644 index 0000000000..782a6036a1 --- /dev/null +++ b/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/example_allowed_exempt.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privileged-allowed-exempt + labels: + app: nginx-privileged +spec: + containers: + - name: nginx + image: safeimages.com/nginx + securityContext: + privileged: true diff --git a/library/pod-security-policy/privileged-containers/suite.yaml b/library/pod-security-policy/privileged-containers/suite.yaml index c2e484fc5f..3dd4ff8639 100644 --- a/library/pod-security-policy/privileged-containers/suite.yaml +++ b/library/pod-security-policy/privileged-containers/suite.yaml @@ -23,3 +23,7 @@ tests: object: samples/psp-privileged-container/update.yaml assertions: - violations: no + - name: exempted-image + object: samples/psp-privileged-container/example_allowed_exempt.yaml + assertions: + - violations: no diff --git a/library/pod-security-policy/privileged-containers/template.yaml b/library/pod-security-policy/privileged-containers/template.yaml index bd8452e679..c5a09674c6 100644 --- a/library/pod-security-policy/privileged-containers/template.yaml +++ b/library/pod-security-policy/privileged-containers/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spspprivilegedcontainer annotations: metadata.gatekeeper.sh/title: "Privileged Container" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Controls the ability of any container to enable privileged mode. Corresponds to the `privileged` field in a PodSecurityPolicy. For more @@ -58,15 +58,18 @@ spec: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + 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.privileged) && container.securityContext.privileged == true) ).map(container, "Privileged container is not allowed: " + container.name +", securityContext: " + container.securityContext) + - name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" validations: - - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + - expression: variables.isUpdate || size(variables.badContainers) == 0 messageExpression: 'variables.badContainers.join("\n")' - engine: Rego source: diff --git a/src/pod-security-policy/privileged-containers/constraint.tmpl b/src/pod-security-policy/privileged-containers/constraint.tmpl index d5c87bb9d3..4711137dde 100644 --- a/src/pod-security-policy/privileged-containers/constraint.tmpl +++ b/src/pod-security-policy/privileged-containers/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spspprivilegedcontainer annotations: metadata.gatekeeper.sh/title: "Privileged Container" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Controls the ability of any container to enable privileged mode. Corresponds to the `privileged` field in a PodSecurityPolicy. For more diff --git a/src/pod-security-policy/privileged-containers/src.cel b/src/pod-security-policy/privileged-containers/src.cel index 99c7b4a450..b0c77fbc4a 100644 --- a/src/pod-security-policy/privileged-containers/src.cel +++ b/src/pod-security-policy/privileged-containers/src.cel @@ -17,13 +17,16 @@ variables: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + 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.privileged) && container.securityContext.privileged == true) ).map(container, "Privileged container is not allowed: " + container.name +", securityContext: " + container.securityContext) +- name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" validations: -- expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' - messageExpression: 'variables.badContainers.join("\n")' \ No newline at end of file +- expression: variables.isUpdate || size(variables.badContainers) == 0 + messageExpression: 'variables.badContainers.join("\n")' diff --git a/website/docs/validation/privileged-containers.md b/website/docs/validation/privileged-containers.md index bf0d8717fa..8354c1976a 100644 --- a/website/docs/validation/privileged-containers.md +++ b/website/docs/validation/privileged-containers.md @@ -16,7 +16,7 @@ metadata: name: k8spspprivilegedcontainer annotations: metadata.gatekeeper.sh/title: "Privileged Container" - metadata.gatekeeper.sh/version: 1.1.0 + metadata.gatekeeper.sh/version: 1.1.1 description: >- Controls the ability of any container to enable privileged mode. Corresponds to the `privileged` field in a PodSecurityPolicy. For more @@ -70,15 +70,18 @@ spec: expression: | (variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container, container.image in variables.exemptImageExplicit || - variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption))) + 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.privileged) && container.securityContext.privileged == true) ).map(container, "Privileged container is not allowed: " + container.name +", securityContext: " + container.securityContext) + - name: isUpdate + expression: has(request.operation) && request.operation == "UPDATE" validations: - - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badContainers) == 0' + - expression: variables.isUpdate || size(variables.badContainers) == 0 messageExpression: 'variables.badContainers.join("\n")' - engine: Rego source: @@ -161,6 +164,9 @@ spec: - apiGroups: [""] kinds: ["Pod"] excludedNamespaces: ["kube-system"] + parameters: + exemptImages: + - "safeimages.com/*" ``` @@ -249,6 +255,32 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/disallowed_ephemeral.yaml ``` +
+
+exempted-image + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-privileged-allowed-exempt + labels: + app: nginx-privileged +spec: + containers: + - name: nginx + image: safeimages.com/nginx + securityContext: + privileged: true + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/privileged-containers/samples/psp-privileged-container/example_allowed_exempt.yaml +``` +
From 7983a1d1706746efe27813555fd7a05938f53482 Mon Sep 17 00:00:00 2001 From: Jaydipkumar Arvindbhai Gabani Date: Tue, 3 Sep 2024 18:52:53 -0700 Subject: [PATCH 18/20] chore: adding CEL for psp-host-filesystem (#547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: adding CEL for psp-host-filesystem Signed-off-by: Jaydip Gabani * updating cel, updating labels on example Signed-off-by: Jaydip Gabani * removing blank lines Signed-off-by: Jaydip Gabani * fixing CEL error Signed-off-by: Jaydip Gabani --------- Signed-off-by: Jaydip Gabani Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- .../disallowed_ephemeral.yaml | 2 - .../psp-host-filesystem/example_allowed.yaml | 2 - .../example_disallowed.yaml | 2 - .../disallowed_ephemeral.yaml | 2 - .../psp-host-filesystem/example_allowed.yaml | 2 - .../example_disallowed.yaml | 2 - .../disallowed_ephemeral.yaml | 2 - .../psp-host-filesystem/example_allowed.yaml | 2 - .../example_disallowed.yaml | 2 - .../host-filesystem/1.1.0/artifacthub-pkg.yml | 22 ++ .../host-filesystem/1.1.0/kustomization.yaml | 2 + .../psp-host-filesystem/constraint.yaml | 13 + .../disallowed_ephemeral.yaml | 16 ++ .../psp-host-filesystem/example_allowed.yaml | 16 ++ .../example_disallowed.yaml | 16 ++ .../samples/psp-host-filesystem/update.yaml | 23 ++ .../host-filesystem/1.1.0/suite.yaml | 25 ++ .../host-filesystem/1.1.0/template.yaml | 181 +++++++++++++ .../disallowed_ephemeral.yaml | 2 - .../psp-host-filesystem/example_allowed.yaml | 2 - .../example_disallowed.yaml | 2 - .../host-filesystem/template.yaml | 249 +++++++++-------- .../host-filesystem/constraint.tmpl | 18 +- .../host-filesystem/src.cel | 28 ++ website/docs/validation/host-filesystem.md | 255 ++++++++++-------- 25 files changed, 636 insertions(+), 252 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/update.yaml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/host-filesystem/1.1.0/template.yaml create mode 100644 src/pod-security-policy/host-filesystem/src.cel diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml index beece55c08..7d18cc85eb 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: ephemeralContainers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_allowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_allowed.yaml index abc60d882b..806101e8c5 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_allowed.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_allowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_disallowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_disallowed.yaml index 53107694f1..51ecf14b52 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_disallowed.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.0/samples/psp-host-filesystem/example_disallowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/disallowed_ephemeral.yaml index beece55c08..7d18cc85eb 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/disallowed_ephemeral.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/disallowed_ephemeral.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: ephemeralContainers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_allowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_allowed.yaml index abc60d882b..806101e8c5 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_allowed.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_allowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_disallowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_disallowed.yaml index 53107694f1..51ecf14b52 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_disallowed.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.1/samples/psp-host-filesystem/example_disallowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/disallowed_ephemeral.yaml index beece55c08..7d18cc85eb 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/disallowed_ephemeral.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/disallowed_ephemeral.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: ephemeralContainers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_allowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_allowed.yaml index abc60d882b..806101e8c5 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_allowed.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_allowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_disallowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_disallowed.yaml index 53107694f1..51ecf14b52 100644 --- a/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_disallowed.yaml +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.0.2/samples/psp-host-filesystem/example_disallowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/artifacthub-pkg.yml new file mode 100644 index 0000000000..7fe2cbdb68 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.0 +name: k8spsphostfilesystem +displayName: Host Filesystem +createdAt: "2024-06-05T20:31:16Z" +description: Controls usage of the host filesystem. Corresponds to the `allowedHostPaths` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems +digest: 3883941aac9b7598a79c9bf6517c77a401b99d165bf584dce604a00eef79f8e7 +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/host-filesystem +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Host Filesystem + Controls usage of the host filesystem. Corresponds to the `allowedHostPaths` field in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/kustomization.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/constraint.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/constraint.yaml new file mode 100644 index 0000000000..7cbd7b824e --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/constraint.yaml @@ -0,0 +1,13 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPHostFilesystem +metadata: + name: psp-host-filesystem +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + allowedHostPaths: + - readOnly: true + pathPrefix: "/foo" diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..7d18cc85eb --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/disallowed_ephemeral.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-filesystem +spec: + ephemeralContainers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /cache + name: cache-volume + readOnly: true + volumes: + - name: cache-volume + hostPath: + path: /tmp # directory location on host diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_allowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_allowed.yaml new file mode 100644 index 0000000000..806101e8c5 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_allowed.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-filesystem +spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /cache + name: cache-volume + readOnly: true + volumes: + - name: cache-volume + hostPath: + path: /foo/bar diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_disallowed.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_disallowed.yaml new file mode 100644 index 0000000000..51ecf14b52 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/example_disallowed.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-host-filesystem +spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /cache + name: cache-volume + readOnly: true + volumes: + - name: cache-volume + hostPath: + path: /tmp # directory location on host diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/update.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/update.yaml new file mode 100644 index 0000000000..68b28a536d --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/samples/psp-host-filesystem/update.yaml @@ -0,0 +1,23 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: nginx-host-filesystem + labels: + app: nginx-host-filesystem-disallowed + spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /cache + name: cache-volume + readOnly: true + volumes: + - name: cache-volume + hostPath: + path: /tmp # directory location on host diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/suite.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/suite.yaml new file mode 100644 index 0000000000..5441df8cc4 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/suite.yaml @@ -0,0 +1,25 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: host-filesystem +tests: + - name: host-filesystem + template: template.yaml + constraint: samples/psp-host-filesystem/constraint.yaml + cases: + - name: example-disallowed + object: samples/psp-host-filesystem/example_disallowed.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/psp-host-filesystem/example_allowed.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/psp-host-filesystem/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/psp-host-filesystem/update.yaml + assertions: + - violations: no diff --git a/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/template.yaml b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/template.yaml new file mode 100644 index 0000000000..6158ec1ca1 --- /dev/null +++ b/artifacthub/library/pod-security-policy/host-filesystem/1.1.0/template.yaml @@ -0,0 +1,181 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spsphostfilesystem + annotations: + metadata.gatekeeper.sh/title: "Host Filesystem" + metadata.gatekeeper.sh/version: 1.1.0 + description: >- + Controls usage of the host filesystem. Corresponds to the + `allowedHostPaths` field in a PodSecurityPolicy. For more information, + see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems +spec: + crd: + spec: + names: + kind: K8sPSPHostFilesystem + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Controls usage of the host filesystem. Corresponds to the + `allowedHostPaths` field in a PodSecurityPolicy. For more information, + see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems + properties: + allowedHostPaths: + type: array + description: "An array of hostpath objects, representing paths and read/write configuration." + items: + type: object + properties: + pathPrefix: + type: string + description: "The path prefix that the host volume must match." + readOnly: + type: boolean + description: "when set to true, any container volumeMounts matching the pathPrefix must include `readOnly: true`." + 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: allContainers + expression: 'variables.containers + variables.initContainers + variables.ephemeralContainers' + - name: allowedPaths + expression: | + !has(variables.params.allowedHostPaths) ? [] : variables.params.allowedHostPaths + - name: volumes + expression: | + variables.anyObject.spec.volumes.filter(volume, has(volume.hostPath)) + - name: badHostPaths + expression: | + variables.volumes.filter(volume, + (size(variables.allowedPaths) == 0) || + !(variables.allowedPaths.exists(allowedPath, + volume.hostPath.path.startsWith(allowedPath.pathPrefix) && ( + (!has(allowedPath.readOnly) || !(allowedPath.readOnly)) || + (has(allowedPath.readOnly) && allowedPath.readOnly && !variables.allContainers.exists(c, + c.volumeMounts.exists(m, m.name == volume.name && (!has(m.readOnly) || !m.readOnly))))))) + ).map(volume, "{ hostPath: { path : " + volume.hostPath.path + " }, name: " + volume.name + "}").map(volume, "HostPath volume " + volume + " is not allowed, pod: " + object.metadata.name + ". Allowed path: " + variables.allowedPaths.map(path, path.pathPrefix + ", readOnly: " + (path.readOnly ? "true" : "false") + "}").join(", ")) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badHostPaths) == 0' + messageExpression: 'variables.badHostPaths.join("\n")' + - engine: Rego + source: + rego: | + package k8spsphostfilesystem + + import data.lib.exclude_update.is_update + + violation[{"msg": msg, "details": {}}] { + # spec.volumes field is immutable. + not is_update(input.review) + + volume := input_hostpath_volumes[_] + allowedPaths := get_allowed_paths(input) + input_hostpath_violation(allowedPaths, volume) + msg := sprintf("HostPath volume %v is not allowed, pod: %v. Allowed path: %v", [volume, input.review.object.metadata.name, allowedPaths]) + } + + input_hostpath_violation(allowedPaths, _) { + # An empty list means all host paths are blocked + allowedPaths == [] + } + input_hostpath_violation(allowedPaths, volume) { + not input_hostpath_allowed(allowedPaths, volume) + } + + get_allowed_paths(arg) = out { + not arg.parameters + out = [] + } + get_allowed_paths(arg) = out { + not arg.parameters.allowedHostPaths + out = [] + } + get_allowed_paths(arg) = out { + out = arg.parameters.allowedHostPaths + } + + input_hostpath_allowed(allowedPaths, volume) { + allowedHostPath := allowedPaths[_] + path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) + not allowedHostPath.readOnly == true + } + + input_hostpath_allowed(allowedPaths, volume) { + allowedHostPath := allowedPaths[_] + path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) + allowedHostPath.readOnly + not writeable_input_volume_mounts(volume.name) + } + + writeable_input_volume_mounts(volume_name) { + container := input_containers[_] + mount := container.volumeMounts[_] + mount.name == volume_name + not mount.readOnly + } + + # This allows "/foo", "/foo/", "/foo/bar" etc., but + # disallows "/fool", "/etc/foo" etc. + path_matches(prefix, path) { + a := path_array(prefix) + b := path_array(path) + prefix_matches(a, b) + } + path_array(p) = out { + p != "/" + out := split(trim(p, "/"), "/") + } + # This handles the special case for "/", since + # split(trim("/", "/"), "/") == [""] + path_array("/") = [] + + prefix_matches(a, b) { + count(a) <= count(b) + not any_not_equal_upto(a, b, count(a)) + } + + any_not_equal_upto(a, b, n) { + a[i] != b[i] + i < n + } + + input_hostpath_volumes[v] { + v := input.review.object.spec.volumes[_] + has_field(v, "hostPath") + } + + # has_field returns whether an object has a field + has_field(object, field) = true { + object[field] + } + 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[_] + } + libs: + - | + package lib.exclude_update + + is_update(review) { + review.operation == "UPDATE" + } diff --git a/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/disallowed_ephemeral.yaml b/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/disallowed_ephemeral.yaml index beece55c08..7d18cc85eb 100644 --- a/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/disallowed_ephemeral.yaml +++ b/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/disallowed_ephemeral.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: ephemeralContainers: - name: nginx diff --git a/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_allowed.yaml b/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_allowed.yaml index abc60d882b..806101e8c5 100644 --- a/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_allowed.yaml +++ b/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_allowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_disallowed.yaml b/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_disallowed.yaml index 53107694f1..51ecf14b52 100644 --- a/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_disallowed.yaml +++ b/library/pod-security-policy/host-filesystem/samples/psp-host-filesystem/example_disallowed.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx diff --git a/library/pod-security-policy/host-filesystem/template.yaml b/library/pod-security-policy/host-filesystem/template.yaml index 5f506c351c..6158ec1ca1 100644 --- a/library/pod-security-policy/host-filesystem/template.yaml +++ b/library/pod-security-policy/host-filesystem/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spsphostfilesystem annotations: metadata.gatekeeper.sh/title: "Host Filesystem" - metadata.gatekeeper.sh/version: 1.0.2 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls usage of the host filesystem. Corresponds to the `allowedHostPaths` field in a PodSecurityPolicy. For more information, @@ -39,110 +39,143 @@ spec: description: "when set to true, any container volumeMounts matching the pathPrefix must include `readOnly: true`." targets: - target: admission.k8s.gatekeeper.sh - rego: | - package k8spsphostfilesystem - - import data.lib.exclude_update.is_update - - violation[{"msg": msg, "details": {}}] { - # spec.volumes field is immutable. - not is_update(input.review) - - volume := input_hostpath_volumes[_] - allowedPaths := get_allowed_paths(input) - input_hostpath_violation(allowedPaths, volume) - msg := sprintf("HostPath volume %v is not allowed, pod: %v. Allowed path: %v", [volume, input.review.object.metadata.name, allowedPaths]) - } - - input_hostpath_violation(allowedPaths, _) { - # An empty list means all host paths are blocked - allowedPaths == [] - } - input_hostpath_violation(allowedPaths, volume) { - not input_hostpath_allowed(allowedPaths, volume) - } - - get_allowed_paths(arg) = out { - not arg.parameters - out = [] - } - get_allowed_paths(arg) = out { - not arg.parameters.allowedHostPaths - out = [] - } - get_allowed_paths(arg) = out { - out = arg.parameters.allowedHostPaths - } - - input_hostpath_allowed(allowedPaths, volume) { - allowedHostPath := allowedPaths[_] - path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) - not allowedHostPath.readOnly == true - } - - input_hostpath_allowed(allowedPaths, volume) { - allowedHostPath := allowedPaths[_] - path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) - allowedHostPath.readOnly - not writeable_input_volume_mounts(volume.name) - } - - writeable_input_volume_mounts(volume_name) { - container := input_containers[_] - mount := container.volumeMounts[_] - mount.name == volume_name - not mount.readOnly - } - - # This allows "/foo", "/foo/", "/foo/bar" etc., but - # disallows "/fool", "/etc/foo" etc. - path_matches(prefix, path) { - a := path_array(prefix) - b := path_array(path) - prefix_matches(a, b) - } - path_array(p) = out { - p != "/" - out := split(trim(p, "/"), "/") - } - # This handles the special case for "/", since - # split(trim("/", "/"), "/") == [""] - path_array("/") = [] - - prefix_matches(a, b) { - count(a) <= count(b) - not any_not_equal_upto(a, b, count(a)) - } - - any_not_equal_upto(a, b, n) { - a[i] != b[i] - i < n - } - - input_hostpath_volumes[v] { - v := input.review.object.spec.volumes[_] - has_field(v, "hostPath") - } - - # has_field returns whether an object has a field - has_field(object, field) = true { - object[field] - } - 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[_] - } - libs: - - | - package lib.exclude_update - - is_update(review) { - review.operation == "UPDATE" - } + 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: allContainers + expression: 'variables.containers + variables.initContainers + variables.ephemeralContainers' + - name: allowedPaths + expression: | + !has(variables.params.allowedHostPaths) ? [] : variables.params.allowedHostPaths + - name: volumes + expression: | + variables.anyObject.spec.volumes.filter(volume, has(volume.hostPath)) + - name: badHostPaths + expression: | + variables.volumes.filter(volume, + (size(variables.allowedPaths) == 0) || + !(variables.allowedPaths.exists(allowedPath, + volume.hostPath.path.startsWith(allowedPath.pathPrefix) && ( + (!has(allowedPath.readOnly) || !(allowedPath.readOnly)) || + (has(allowedPath.readOnly) && allowedPath.readOnly && !variables.allContainers.exists(c, + c.volumeMounts.exists(m, m.name == volume.name && (!has(m.readOnly) || !m.readOnly))))))) + ).map(volume, "{ hostPath: { path : " + volume.hostPath.path + " }, name: " + volume.name + "}").map(volume, "HostPath volume " + volume + " is not allowed, pod: " + object.metadata.name + ". Allowed path: " + variables.allowedPaths.map(path, path.pathPrefix + ", readOnly: " + (path.readOnly ? "true" : "false") + "}").join(", ")) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badHostPaths) == 0' + messageExpression: 'variables.badHostPaths.join("\n")' + - engine: Rego + source: + rego: | + package k8spsphostfilesystem + + import data.lib.exclude_update.is_update + + violation[{"msg": msg, "details": {}}] { + # spec.volumes field is immutable. + not is_update(input.review) + + volume := input_hostpath_volumes[_] + allowedPaths := get_allowed_paths(input) + input_hostpath_violation(allowedPaths, volume) + msg := sprintf("HostPath volume %v is not allowed, pod: %v. Allowed path: %v", [volume, input.review.object.metadata.name, allowedPaths]) + } + + input_hostpath_violation(allowedPaths, _) { + # An empty list means all host paths are blocked + allowedPaths == [] + } + input_hostpath_violation(allowedPaths, volume) { + not input_hostpath_allowed(allowedPaths, volume) + } + + get_allowed_paths(arg) = out { + not arg.parameters + out = [] + } + get_allowed_paths(arg) = out { + not arg.parameters.allowedHostPaths + out = [] + } + get_allowed_paths(arg) = out { + out = arg.parameters.allowedHostPaths + } + + input_hostpath_allowed(allowedPaths, volume) { + allowedHostPath := allowedPaths[_] + path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) + not allowedHostPath.readOnly == true + } + + input_hostpath_allowed(allowedPaths, volume) { + allowedHostPath := allowedPaths[_] + path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) + allowedHostPath.readOnly + not writeable_input_volume_mounts(volume.name) + } + + writeable_input_volume_mounts(volume_name) { + container := input_containers[_] + mount := container.volumeMounts[_] + mount.name == volume_name + not mount.readOnly + } + + # This allows "/foo", "/foo/", "/foo/bar" etc., but + # disallows "/fool", "/etc/foo" etc. + path_matches(prefix, path) { + a := path_array(prefix) + b := path_array(path) + prefix_matches(a, b) + } + path_array(p) = out { + p != "/" + out := split(trim(p, "/"), "/") + } + # This handles the special case for "/", since + # split(trim("/", "/"), "/") == [""] + path_array("/") = [] + + prefix_matches(a, b) { + count(a) <= count(b) + not any_not_equal_upto(a, b, count(a)) + } + + any_not_equal_upto(a, b, n) { + a[i] != b[i] + i < n + } + + input_hostpath_volumes[v] { + v := input.review.object.spec.volumes[_] + has_field(v, "hostPath") + } + + # has_field returns whether an object has a field + has_field(object, field) = true { + object[field] + } + 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[_] + } + libs: + - | + package lib.exclude_update + + is_update(review) { + review.operation == "UPDATE" + } diff --git a/src/pod-security-policy/host-filesystem/constraint.tmpl b/src/pod-security-policy/host-filesystem/constraint.tmpl index d442ec3315..ad5b446c3c 100644 --- a/src/pod-security-policy/host-filesystem/constraint.tmpl +++ b/src/pod-security-policy/host-filesystem/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spsphostfilesystem annotations: metadata.gatekeeper.sh/title: "Host Filesystem" - metadata.gatekeeper.sh/version: 1.0.2 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls usage of the host filesystem. Corresponds to the `allowedHostPaths` field in a PodSecurityPolicy. For more information, @@ -39,8 +39,14 @@ spec: description: "when set to true, any container volumeMounts matching the pathPrefix must include `readOnly: true`." targets: - target: admission.k8s.gatekeeper.sh - rego: | -{{ file.Read "src/pod-security-policy/host-filesystem/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }} - libs: - - | -{{ file.Read "src/pod-security-policy/host-filesystem/lib_exclude_update.rego" | strings.Indent 10 | strings.TrimSuffix "\n" }} + code: + - engine: K8sNativeValidation + source: +{{ file.Read "src/pod-security-policy/host-filesystem/src.cel" | strings.Indent 10 | strings.TrimSuffix "\n" }} + - engine: Rego + source: + rego: | +{{ file.Read "src/pod-security-policy/host-filesystem/src.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} + libs: + - | +{{ file.Read "src/pod-security-policy/host-filesystem/lib_exclude_update.rego" | strings.Indent 14 | strings.TrimSuffix "\n" }} diff --git a/src/pod-security-policy/host-filesystem/src.cel b/src/pod-security-policy/host-filesystem/src.cel new file mode 100644 index 0000000000..3497a3e283 --- /dev/null +++ b/src/pod-security-policy/host-filesystem/src.cel @@ -0,0 +1,28 @@ +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: allContainers + expression: 'variables.containers + variables.initContainers + variables.ephemeralContainers' +- name: allowedPaths + expression: | + !has(variables.params.allowedHostPaths) ? [] : variables.params.allowedHostPaths +- name: volumes + expression: | + variables.anyObject.spec.volumes.filter(volume, has(volume.hostPath)) +- name: badHostPaths + expression: | + variables.volumes.filter(volume, + (size(variables.allowedPaths) == 0) || + !(variables.allowedPaths.exists(allowedPath, + volume.hostPath.path.startsWith(allowedPath.pathPrefix) && ( + (!has(allowedPath.readOnly) || !(allowedPath.readOnly)) || + (has(allowedPath.readOnly) && allowedPath.readOnly && !variables.allContainers.exists(c, + c.volumeMounts.exists(m, m.name == volume.name && (!has(m.readOnly) || !m.readOnly))))))) + ).map(volume, "{ hostPath: { path : " + volume.hostPath.path + " }, name: " + volume.name + "}").map(volume, "HostPath volume " + volume + " is not allowed, pod: " + object.metadata.name + ". Allowed path: " + variables.allowedPaths.map(path, path.pathPrefix + ", readOnly: " + (path.readOnly ? "true" : "false") + "}").join(", ")) +validations: +- expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badHostPaths) == 0' + messageExpression: 'variables.badHostPaths.join("\n")' \ No newline at end of file diff --git a/website/docs/validation/host-filesystem.md b/website/docs/validation/host-filesystem.md index 57e7d436f8..07704e0bd2 100644 --- a/website/docs/validation/host-filesystem.md +++ b/website/docs/validation/host-filesystem.md @@ -16,7 +16,7 @@ metadata: name: k8spsphostfilesystem annotations: metadata.gatekeeper.sh/title: "Host Filesystem" - metadata.gatekeeper.sh/version: 1.0.2 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls usage of the host filesystem. Corresponds to the `allowedHostPaths` field in a PodSecurityPolicy. For more information, @@ -51,113 +51,146 @@ spec: description: "when set to true, any container volumeMounts matching the pathPrefix must include `readOnly: true`." targets: - target: admission.k8s.gatekeeper.sh - rego: | - package k8spsphostfilesystem - - import data.lib.exclude_update.is_update - - violation[{"msg": msg, "details": {}}] { - # spec.volumes field is immutable. - not is_update(input.review) - - volume := input_hostpath_volumes[_] - allowedPaths := get_allowed_paths(input) - input_hostpath_violation(allowedPaths, volume) - msg := sprintf("HostPath volume %v is not allowed, pod: %v. Allowed path: %v", [volume, input.review.object.metadata.name, allowedPaths]) - } - - input_hostpath_violation(allowedPaths, _) { - # An empty list means all host paths are blocked - allowedPaths == [] - } - input_hostpath_violation(allowedPaths, volume) { - not input_hostpath_allowed(allowedPaths, volume) - } - - get_allowed_paths(arg) = out { - not arg.parameters - out = [] - } - get_allowed_paths(arg) = out { - not arg.parameters.allowedHostPaths - out = [] - } - get_allowed_paths(arg) = out { - out = arg.parameters.allowedHostPaths - } - - input_hostpath_allowed(allowedPaths, volume) { - allowedHostPath := allowedPaths[_] - path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) - not allowedHostPath.readOnly == true - } - - input_hostpath_allowed(allowedPaths, volume) { - allowedHostPath := allowedPaths[_] - path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) - allowedHostPath.readOnly - not writeable_input_volume_mounts(volume.name) - } - - writeable_input_volume_mounts(volume_name) { - container := input_containers[_] - mount := container.volumeMounts[_] - mount.name == volume_name - not mount.readOnly - } - - # This allows "/foo", "/foo/", "/foo/bar" etc., but - # disallows "/fool", "/etc/foo" etc. - path_matches(prefix, path) { - a := path_array(prefix) - b := path_array(path) - prefix_matches(a, b) - } - path_array(p) = out { - p != "/" - out := split(trim(p, "/"), "/") - } - # This handles the special case for "/", since - # split(trim("/", "/"), "/") == [""] - path_array("/") = [] - - prefix_matches(a, b) { - count(a) <= count(b) - not any_not_equal_upto(a, b, count(a)) - } - - any_not_equal_upto(a, b, n) { - a[i] != b[i] - i < n - } - - input_hostpath_volumes[v] { - v := input.review.object.spec.volumes[_] - has_field(v, "hostPath") - } - - # has_field returns whether an object has a field - has_field(object, field) = true { - object[field] - } - 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[_] - } - libs: - - | - package lib.exclude_update - - is_update(review) { - review.operation == "UPDATE" - } + 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: allContainers + expression: 'variables.containers + variables.initContainers + variables.ephemeralContainers' + - name: allowedPaths + expression: | + !has(variables.params.allowedHostPaths) ? [] : variables.params.allowedHostPaths + - name: volumes + expression: | + variables.anyObject.spec.volumes.filter(volume, has(volume.hostPath)) + - name: badHostPaths + expression: | + variables.volumes.filter(volume, + (size(variables.allowedPaths) == 0) || + !(variables.allowedPaths.exists(allowedPath, + volume.hostPath.path.startsWith(allowedPath.pathPrefix) && ( + (!has(allowedPath.readOnly) || !(allowedPath.readOnly)) || + (has(allowedPath.readOnly) && allowedPath.readOnly && !variables.allContainers.exists(c, + c.volumeMounts.exists(m, m.name == volume.name && (!has(m.readOnly) || !m.readOnly))))))) + ).map(volume, "{ hostPath: { path : " + volume.hostPath.path + " }, name: " + volume.name + "}").map(volume, "HostPath volume " + volume + " is not allowed, pod: " + object.metadata.name + ". Allowed path: " + variables.allowedPaths.map(path, path.pathPrefix + ", readOnly: " + (path.readOnly ? "true" : "false") + "}").join(", ")) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.badHostPaths) == 0' + messageExpression: 'variables.badHostPaths.join("\n")' + - engine: Rego + source: + rego: | + package k8spsphostfilesystem + + import data.lib.exclude_update.is_update + + violation[{"msg": msg, "details": {}}] { + # spec.volumes field is immutable. + not is_update(input.review) + + volume := input_hostpath_volumes[_] + allowedPaths := get_allowed_paths(input) + input_hostpath_violation(allowedPaths, volume) + msg := sprintf("HostPath volume %v is not allowed, pod: %v. Allowed path: %v", [volume, input.review.object.metadata.name, allowedPaths]) + } + + input_hostpath_violation(allowedPaths, _) { + # An empty list means all host paths are blocked + allowedPaths == [] + } + input_hostpath_violation(allowedPaths, volume) { + not input_hostpath_allowed(allowedPaths, volume) + } + + get_allowed_paths(arg) = out { + not arg.parameters + out = [] + } + get_allowed_paths(arg) = out { + not arg.parameters.allowedHostPaths + out = [] + } + get_allowed_paths(arg) = out { + out = arg.parameters.allowedHostPaths + } + + input_hostpath_allowed(allowedPaths, volume) { + allowedHostPath := allowedPaths[_] + path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) + not allowedHostPath.readOnly == true + } + + input_hostpath_allowed(allowedPaths, volume) { + allowedHostPath := allowedPaths[_] + path_matches(allowedHostPath.pathPrefix, volume.hostPath.path) + allowedHostPath.readOnly + not writeable_input_volume_mounts(volume.name) + } + + writeable_input_volume_mounts(volume_name) { + container := input_containers[_] + mount := container.volumeMounts[_] + mount.name == volume_name + not mount.readOnly + } + + # This allows "/foo", "/foo/", "/foo/bar" etc., but + # disallows "/fool", "/etc/foo" etc. + path_matches(prefix, path) { + a := path_array(prefix) + b := path_array(path) + prefix_matches(a, b) + } + path_array(p) = out { + p != "/" + out := split(trim(p, "/"), "/") + } + # This handles the special case for "/", since + # split(trim("/", "/"), "/") == [""] + path_array("/") = [] + + prefix_matches(a, b) { + count(a) <= count(b) + not any_not_equal_upto(a, b, count(a)) + } + + any_not_equal_upto(a, b, n) { + a[i] != b[i] + i < n + } + + input_hostpath_volumes[v] { + v := input.review.object.spec.volumes[_] + has_field(v, "hostPath") + } + + # has_field returns whether an object has a field + has_field(object, field) = true { + object[field] + } + 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[_] + } + libs: + - | + package lib.exclude_update + + is_update(review) { + review.operation == "UPDATE" + } ``` @@ -205,8 +238,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx @@ -237,8 +268,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: containers: - name: nginx @@ -269,8 +298,6 @@ apiVersion: v1 kind: Pod metadata: name: nginx-host-filesystem - labels: - app: nginx-host-filesystem-disallowed spec: ephemeralContainers: - name: nginx From d59972f3a9cc09feb046758155c84653df3fc46f Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Tue, 3 Sep 2024 19:12:12 -0700 Subject: [PATCH 19/20] Add CEL to K8sPSPCapabilities template (#535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add CEL to K8sPSPCapabilities template Signed-off-by: Max Smythe * bump minor version Signed-off-by: Max Smythe --------- Signed-off-by: Max Smythe Co-authored-by: Jaydipkumar Arvindbhai Gabani Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- .../capabilities/1.1.0/artifacthub-pkg.yml | 22 ++ .../capabilities/1.1.0/kustomization.yaml | 2 + .../samples/capabilities-demo/constraint.yaml | 14 + .../disallowed_ephemeral.yaml | 21 ++ .../capabilities-demo/example_allowed.yaml | 22 ++ .../capabilities-demo/example_disallowed.yaml | 21 ++ .../samples/capabilities-demo/update.yaml | 26 ++ .../capabilities/1.1.0/suite.yaml | 25 ++ .../capabilities/1.1.0/template.yaml | 224 ++++++++++++++ .../capabilities/template.yaml | 288 +++++++++++------- .../capabilities/constraint.tmpl | 22 +- src/pod-security-policy/capabilities/src.cel | 59 ++++ website/docs/validation/capabilities.md | 288 +++++++++++------- 13 files changed, 802 insertions(+), 232 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/update.yaml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/capabilities/1.1.0/template.yaml create mode 100644 src/pod-security-policy/capabilities/src.cel diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/artifacthub-pkg.yml new file mode 100644 index 0000000000..5523381c37 --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.0 +name: k8spspcapabilities +displayName: Capabilities +createdAt: "2024-05-29T23:37:22Z" +description: Controls Linux capabilities on containers. Corresponds to the `allowedCapabilities` and `requiredDropCapabilities` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities +digest: 1b837e4add0952bb782bf0d0bc5e12ac0b0543ee4d23f88e9a282b531bfb8ff5 +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/capabilities +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # Capabilities + Controls Linux capabilities on containers. Corresponds to the `allowedCapabilities` and `requiredDropCapabilities` fields in a PodSecurityPolicy. For more information, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/capabilities/1.1.0/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/kustomization.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/constraint.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/constraint.yaml new file mode 100644 index 0000000000..3f856082f1 --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/constraint.yaml @@ -0,0 +1,14 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPCapabilities +metadata: + name: capabilities-demo +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + namespaces: + - "default" + parameters: + allowedCapabilities: ["something"] + requiredDropCapabilities: ["must_drop"] diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..5467c826ed --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/disallowed_ephemeral.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: opa-disallowed + labels: + owner: me.agilebank.demo +spec: + ephemeralContainers: + - name: opa + image: openpolicyagent/opa:0.9.2 + args: + - "run" + - "--server" + - "--addr=localhost:8080" + securityContext: + capabilities: + add: ["disallowedcapability"] + resources: + limits: + cpu: "100m" + memory: "30Mi" diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_allowed.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_allowed.yaml new file mode 100644 index 0000000000..41bf6a0ed9 --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_allowed.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Pod +metadata: + name: opa-allowed + labels: + owner: me.agilebank.demo +spec: + containers: + - name: opa + image: openpolicyagent/opa:0.9.2 + args: + - "run" + - "--server" + - "--addr=localhost:8080" + securityContext: + capabilities: + add: ["something"] + drop: ["must_drop", "another_one"] + resources: + limits: + cpu: "100m" + memory: "30Mi" diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_disallowed.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_disallowed.yaml new file mode 100644 index 0000000000..fdd8861897 --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/example_disallowed.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: opa-disallowed + labels: + owner: me.agilebank.demo +spec: + containers: + - name: opa + image: openpolicyagent/opa:0.9.2 + args: + - "run" + - "--server" + - "--addr=localhost:8080" + securityContext: + capabilities: + add: ["disallowedcapability"] + resources: + limits: + cpu: "100m" + memory: "30Mi" \ No newline at end of file diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/update.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/update.yaml new file mode 100644 index 0000000000..df8ea0070a --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/samples/capabilities-demo/update.yaml @@ -0,0 +1,26 @@ +kind: AdmissionReview +apiVersion: admission.k8s.io/v1beta1 +request: + operation: "UPDATE" + object: + apiVersion: v1 + kind: Pod + metadata: + name: opa-disallowed + labels: + owner: me.agilebank.demo + spec: + containers: + - name: opa + image: openpolicyagent/opa:0.9.2 + args: + - "run" + - "--server" + - "--addr=localhost:8080" + securityContext: + capabilities: + add: ["disallowedcapability"] + resources: + limits: + cpu: "100m" + memory: "30Mi" diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/suite.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/suite.yaml new file mode 100644 index 0000000000..48c2fcb46b --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/suite.yaml @@ -0,0 +1,25 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: capabilities +tests: + - name: capabilities + template: template.yaml + constraint: samples/capabilities-demo/constraint.yaml + cases: + - name: example-disallowed + object: samples/capabilities-demo/example_disallowed.yaml + assertions: + - violations: yes + - name: example-allowed + object: samples/capabilities-demo/example_allowed.yaml + assertions: + - violations: no + - name: disallowed-ephemeral + object: samples/capabilities-demo/disallowed_ephemeral.yaml + assertions: + - violations: yes + - name: update + object: samples/capabilities-demo/update.yaml + assertions: + - violations: no diff --git a/artifacthub/library/pod-security-policy/capabilities/1.1.0/template.yaml b/artifacthub/library/pod-security-policy/capabilities/1.1.0/template.yaml new file mode 100644 index 0000000000..a9766fd600 --- /dev/null +++ b/artifacthub/library/pod-security-policy/capabilities/1.1.0/template.yaml @@ -0,0 +1,224 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spspcapabilities + annotations: + metadata.gatekeeper.sh/title: "Capabilities" + metadata.gatekeeper.sh/version: 1.1.0 + description: >- + Controls Linux capabilities on containers. Corresponds to the + `allowedCapabilities` and `requiredDropCapabilities` fields in a + PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities +spec: + crd: + spec: + names: + kind: K8sPSPCapabilities + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Controls Linux capabilities on containers. Corresponds to the + `allowedCapabilities` and `requiredDropCapabilities` fields in a + PodSecurityPolicy. For more information, see + https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities + 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 + allowedCapabilities: + type: array + description: "A list of Linux capabilities that can be added to a container." + items: + type: string + requiredDropCapabilities: + type: array + description: "A list of Linux capabilities that are required to be dropped from a container." + 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: allContainers + expression: 'variables.containers + variables.initContainers + variables.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: allowedCapabilities + expression: 'has(variables.params.allowedCapabilities) ? variables.params.allowedCapabilities : []' + - name: allCapabilitiesAllowed + expression: '"*" in variables.allowedCapabilities' + - name: disallowedCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + !variables.allCapabilitiesAllowed && has(container.securityContext) && has(container.securityContext.capabilities) && has(container.securityContext.capabilities.add) && + container.securityContext.capabilities.add.exists(capability, !(capability in variables.allowedCapabilities)), + [container.name, dyn(container.securityContext.capabilities.add.filter(capability, !(capability in variables.allowedCapabilities)).join(", "))] + ) + - name: requiredDropCapabilities + expression: 'has(variables.params.requiredDropCapabilities) ? variables.params.requiredDropCapabilities : []' + - name: missingDropCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + size(variables.requiredDropCapabilities) > 0 && ( + !has(container.securityContext) || !has(container.securityContext.capabilities) || !has(container.securityContext.capabilities.drop) || ( + !("all" in container.securityContext.capabilities.drop) && + variables.requiredDropCapabilities.exists(capability, !(capability in container.securityContext.capabilities.drop)) + ) + ), + [container.name, + !has(container.securityContext) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities.drop) ? variables.requiredDropCapabilities : + variables.requiredDropCapabilities.filter(capability, !(capability in container.securityContext.capabilities.drop)) + ] + ) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.disallowedCapabilitiesByContainer) == 0' + messageExpression: | + "containers have disallowed capabilities: " + variables.disallowedCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1] + "]}").join(", ") + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.missingDropCapabilitiesByContainer) == 0' + messageExpression: | + "containers are not dropping all required capabilities: " + variables.missingDropCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1].join(", ") + "]}").join(", ") + - engine: Rego + source: + rego: | + package capabilities + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg}] { + # spec.containers.securityContext.capabilities field is immutable. + not is_update(input.review) + + container := input.review.object.spec.containers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.containers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.initContainers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("init container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.initContainers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("init container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.ephemeralContainers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("ephemeral container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.ephemeralContainers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("ephemeral container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + has_disallowed_capabilities(container) { + allowed := {c | c := lower(input.parameters.allowedCapabilities[_])} + not allowed["*"] + capabilities := {c | c := lower(container.securityContext.capabilities.add[_])} + + count(capabilities - allowed) > 0 + } + + missing_drop_capabilities(container) { + must_drop := {c | c := lower(input.parameters.requiredDropCapabilities[_])} + all := {"all"} + dropped := {c | c := lower(container.securityContext.capabilities.drop[_])} + + count(must_drop - dropped) > 0 + count(all - dropped) > 0 + } + + get_default(obj, param, _) := obj[param] + + get_default(obj, param, _default) := _default { + not obj[param] + not obj[param] == false + } + 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/capabilities/template.yaml b/library/pod-security-policy/capabilities/template.yaml index 2501a1538e..a9766fd600 100644 --- a/library/pod-security-policy/capabilities/template.yaml +++ b/library/pod-security-policy/capabilities/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spspcapabilities annotations: metadata.gatekeeper.sh/title: "Capabilities" - metadata.gatekeeper.sh/version: 1.0.2 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls Linux capabilities on containers. Corresponds to the `allowedCapabilities` and `requiredDropCapabilities` fields in a @@ -47,114 +47,178 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | - package capabilities - - import data.lib.exclude_update.is_update - import data.lib.exempt_container.is_exempt - - violation[{"msg": msg}] { - # spec.containers.securityContext.capabilities field is immutable. - not is_update(input.review) - - container := input.review.object.spec.containers[_] - not is_exempt(container) - has_disallowed_capabilities(container) - msg := sprintf("container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) - } - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.containers[_] - not is_exempt(container) - missing_drop_capabilities(container) - msg := sprintf("container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) - } - - - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.initContainers[_] - not is_exempt(container) - has_disallowed_capabilities(container) - msg := sprintf("init container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) - } - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.initContainers[_] - not is_exempt(container) - missing_drop_capabilities(container) - msg := sprintf("init container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) - } - - - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.ephemeralContainers[_] - not is_exempt(container) - has_disallowed_capabilities(container) - msg := sprintf("ephemeral container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) - } - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.ephemeralContainers[_] - not is_exempt(container) - missing_drop_capabilities(container) - msg := sprintf("ephemeral container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) - } - - - has_disallowed_capabilities(container) { - allowed := {c | c := lower(input.parameters.allowedCapabilities[_])} - not allowed["*"] - capabilities := {c | c := lower(container.securityContext.capabilities.add[_])} - - count(capabilities - allowed) > 0 - } - - missing_drop_capabilities(container) { - must_drop := {c | c := lower(input.parameters.requiredDropCapabilities[_])} - all := {"all"} - dropped := {c | c := lower(container.securityContext.capabilities.drop[_])} - - count(must_drop - dropped) > 0 - count(all - dropped) > 0 - } - - get_default(obj, param, _) := obj[param] - - get_default(obj, param, _default) := _default { - not obj[param] - not obj[param] == false - } - 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: allContainers + expression: 'variables.containers + variables.initContainers + variables.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: allowedCapabilities + expression: 'has(variables.params.allowedCapabilities) ? variables.params.allowedCapabilities : []' + - name: allCapabilitiesAllowed + expression: '"*" in variables.allowedCapabilities' + - name: disallowedCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + !variables.allCapabilitiesAllowed && has(container.securityContext) && has(container.securityContext.capabilities) && has(container.securityContext.capabilities.add) && + container.securityContext.capabilities.add.exists(capability, !(capability in variables.allowedCapabilities)), + [container.name, dyn(container.securityContext.capabilities.add.filter(capability, !(capability in variables.allowedCapabilities)).join(", "))] + ) + - name: requiredDropCapabilities + expression: 'has(variables.params.requiredDropCapabilities) ? variables.params.requiredDropCapabilities : []' + - name: missingDropCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + size(variables.requiredDropCapabilities) > 0 && ( + !has(container.securityContext) || !has(container.securityContext.capabilities) || !has(container.securityContext.capabilities.drop) || ( + !("all" in container.securityContext.capabilities.drop) && + variables.requiredDropCapabilities.exists(capability, !(capability in container.securityContext.capabilities.drop)) + ) + ), + [container.name, + !has(container.securityContext) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities.drop) ? variables.requiredDropCapabilities : + variables.requiredDropCapabilities.filter(capability, !(capability in container.securityContext.capabilities.drop)) + ] + ) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.disallowedCapabilitiesByContainer) == 0' + messageExpression: | + "containers have disallowed capabilities: " + variables.disallowedCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1] + "]}").join(", ") + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.missingDropCapabilitiesByContainer) == 0' + messageExpression: | + "containers are not dropping all required capabilities: " + variables.missingDropCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1].join(", ") + "]}").join(", ") + - engine: Rego + source: + rego: | + package capabilities + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg}] { + # spec.containers.securityContext.capabilities field is immutable. + not is_update(input.review) + + container := input.review.object.spec.containers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.containers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.initContainers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("init container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.initContainers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("init container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.ephemeralContainers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("ephemeral container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.ephemeralContainers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("ephemeral container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + has_disallowed_capabilities(container) { + allowed := {c | c := lower(input.parameters.allowedCapabilities[_])} + not allowed["*"] + capabilities := {c | c := lower(container.securityContext.capabilities.add[_])} + + count(capabilities - allowed) > 0 + } + + missing_drop_capabilities(container) { + must_drop := {c | c := lower(input.parameters.requiredDropCapabilities[_])} + all := {"all"} + dropped := {c | c := lower(container.securityContext.capabilities.drop[_])} + + count(must_drop - dropped) > 0 + count(all - dropped) > 0 + } + + get_default(obj, param, _) := obj[param] + + get_default(obj, param, _default) := _default { + not obj[param] + not obj[param] == false + } + 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/src/pod-security-policy/capabilities/constraint.tmpl b/src/pod-security-policy/capabilities/constraint.tmpl index 9ca6bed261..02c8abe321 100644 --- a/src/pod-security-policy/capabilities/constraint.tmpl +++ b/src/pod-security-policy/capabilities/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spspcapabilities annotations: metadata.gatekeeper.sh/title: "Capabilities" - metadata.gatekeeper.sh/version: 1.0.2 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls Linux capabilities on containers. Corresponds to the `allowedCapabilities` and `requiredDropCapabilities` fields in a @@ -47,10 +47,16 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | -{{ file.Read "src/pod-security-policy/capabilities/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }} - libs: - - | -{{ file.Read "src/pod-security-policy/capabilities/lib_exclude_update.rego" | strings.Indent 10 | strings.TrimSuffix "\n" }} - - | -{{ file.Read "src/pod-security-policy/capabilities/lib_exempt_container.rego" | strings.Indent 10 | strings.TrimSuffix "\n" }} + code: + - engine: K8sNativeValidation + source: +{{ file.Read "src/pod-security-policy/capabilities/src.cel" | strings.Indent 10 | strings.TrimSuffix "\n" }} + - engine: Rego + source: + rego: | +{{ file.Read "src/pod-security-policy/capabilities/src.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} + libs: + - | +{{ file.Read "src/pod-security-policy/capabilities/lib_exclude_update.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} + - | +{{ file.Read "src/pod-security-policy/capabilities/lib_exempt_container.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} diff --git a/src/pod-security-policy/capabilities/src.cel b/src/pod-security-policy/capabilities/src.cel new file mode 100644 index 0000000000..42c70ff6ae --- /dev/null +++ b/src/pod-security-policy/capabilities/src.cel @@ -0,0 +1,59 @@ +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: allContainers + expression: 'variables.containers + variables.initContainers + variables.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: allowedCapabilities + expression: 'has(variables.params.allowedCapabilities) ? variables.params.allowedCapabilities : []' +- name: allCapabilitiesAllowed + expression: '"*" in variables.allowedCapabilities' +- name: disallowedCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + !variables.allCapabilitiesAllowed && has(container.securityContext) && has(container.securityContext.capabilities) && has(container.securityContext.capabilities.add) && + container.securityContext.capabilities.add.exists(capability, !(capability in variables.allowedCapabilities)), + [container.name, dyn(container.securityContext.capabilities.add.filter(capability, !(capability in variables.allowedCapabilities)).join(", "))] + ) +- name: requiredDropCapabilities + expression: 'has(variables.params.requiredDropCapabilities) ? variables.params.requiredDropCapabilities : []' +- name: missingDropCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + size(variables.requiredDropCapabilities) > 0 && ( + !has(container.securityContext) || !has(container.securityContext.capabilities) || !has(container.securityContext.capabilities.drop) || ( + !("all" in container.securityContext.capabilities.drop) && + variables.requiredDropCapabilities.exists(capability, !(capability in container.securityContext.capabilities.drop)) + ) + ), + [container.name, + !has(container.securityContext) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities.drop) ? variables.requiredDropCapabilities : + variables.requiredDropCapabilities.filter(capability, !(capability in container.securityContext.capabilities.drop)) + ] + ) +validations: +- expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.disallowedCapabilitiesByContainer) == 0' + messageExpression: | + "containers have disallowed capabilities: " + variables.disallowedCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1] + "]}").join(", ") +- expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.missingDropCapabilitiesByContainer) == 0' + messageExpression: | + "containers are not dropping all required capabilities: " + variables.missingDropCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1].join(", ") + "]}").join(", ") \ No newline at end of file diff --git a/website/docs/validation/capabilities.md b/website/docs/validation/capabilities.md index 5048c66273..b119ae89be 100644 --- a/website/docs/validation/capabilities.md +++ b/website/docs/validation/capabilities.md @@ -16,7 +16,7 @@ metadata: name: k8spspcapabilities annotations: metadata.gatekeeper.sh/title: "Capabilities" - metadata.gatekeeper.sh/version: 1.0.2 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Controls Linux capabilities on containers. Corresponds to the `allowedCapabilities` and `requiredDropCapabilities` fields in a @@ -59,117 +59,181 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | - package capabilities - - import data.lib.exclude_update.is_update - import data.lib.exempt_container.is_exempt - - violation[{"msg": msg}] { - # spec.containers.securityContext.capabilities field is immutable. - not is_update(input.review) - - container := input.review.object.spec.containers[_] - not is_exempt(container) - has_disallowed_capabilities(container) - msg := sprintf("container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) - } - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.containers[_] - not is_exempt(container) - missing_drop_capabilities(container) - msg := sprintf("container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) - } - - - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.initContainers[_] - not is_exempt(container) - has_disallowed_capabilities(container) - msg := sprintf("init container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) - } - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.initContainers[_] - not is_exempt(container) - missing_drop_capabilities(container) - msg := sprintf("init container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) - } - - - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.ephemeralContainers[_] - not is_exempt(container) - has_disallowed_capabilities(container) - msg := sprintf("ephemeral container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) - } - - violation[{"msg": msg}] { - not is_update(input.review) - container := input.review.object.spec.ephemeralContainers[_] - not is_exempt(container) - missing_drop_capabilities(container) - msg := sprintf("ephemeral container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) - } - - - has_disallowed_capabilities(container) { - allowed := {c | c := lower(input.parameters.allowedCapabilities[_])} - not allowed["*"] - capabilities := {c | c := lower(container.securityContext.capabilities.add[_])} - - count(capabilities - allowed) > 0 - } - - missing_drop_capabilities(container) { - must_drop := {c | c := lower(input.parameters.requiredDropCapabilities[_])} - all := {"all"} - dropped := {c | c := lower(container.securityContext.capabilities.drop[_])} - - count(must_drop - dropped) > 0 - count(all - dropped) > 0 - } - - get_default(obj, param, _) := obj[param] - - get_default(obj, param, _default) := _default { - not obj[param] - not obj[param] == false - } - 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: allContainers + expression: 'variables.containers + variables.initContainers + variables.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: allowedCapabilities + expression: 'has(variables.params.allowedCapabilities) ? variables.params.allowedCapabilities : []' + - name: allCapabilitiesAllowed + expression: '"*" in variables.allowedCapabilities' + - name: disallowedCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + !variables.allCapabilitiesAllowed && has(container.securityContext) && has(container.securityContext.capabilities) && has(container.securityContext.capabilities.add) && + container.securityContext.capabilities.add.exists(capability, !(capability in variables.allowedCapabilities)), + [container.name, dyn(container.securityContext.capabilities.add.filter(capability, !(capability in variables.allowedCapabilities)).join(", "))] + ) + - name: requiredDropCapabilities + expression: 'has(variables.params.requiredDropCapabilities) ? variables.params.requiredDropCapabilities : []' + - name: missingDropCapabilitiesByContainer + expression: | + variables.allContainers.map(container, !(container.image in variables.exemptImages) && + size(variables.requiredDropCapabilities) > 0 && ( + !has(container.securityContext) || !has(container.securityContext.capabilities) || !has(container.securityContext.capabilities.drop) || ( + !("all" in container.securityContext.capabilities.drop) && + variables.requiredDropCapabilities.exists(capability, !(capability in container.securityContext.capabilities.drop)) + ) + ), + [container.name, + !has(container.securityContext) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities) ? variables.requiredDropCapabilities : + !has(container.securityContext.capabilities.drop) ? variables.requiredDropCapabilities : + variables.requiredDropCapabilities.filter(capability, !(capability in container.securityContext.capabilities.drop)) + ] + ) + validations: + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.disallowedCapabilitiesByContainer) == 0' + messageExpression: | + "containers have disallowed capabilities: " + variables.disallowedCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1] + "]}").join(", ") + - expression: '(has(request.operation) && request.operation == "UPDATE") || size(variables.missingDropCapabilitiesByContainer) == 0' + messageExpression: | + "containers are not dropping all required capabilities: " + variables.missingDropCapabilitiesByContainer.map(pair, "{container: " + pair[0] + ", capabilities: [" + pair[1].join(", ") + "]}").join(", ") + - engine: Rego + source: + rego: | + package capabilities + + import data.lib.exclude_update.is_update + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg}] { + # spec.containers.securityContext.capabilities field is immutable. + not is_update(input.review) + + container := input.review.object.spec.containers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.containers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.initContainers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("init container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.initContainers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("init container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.ephemeralContainers[_] + not is_exempt(container) + has_disallowed_capabilities(container) + msg := sprintf("ephemeral container <%v> has a disallowed capability. Allowed capabilities are %v", [container.name, get_default(input.parameters, "allowedCapabilities", "NONE")]) + } + + violation[{"msg": msg}] { + not is_update(input.review) + container := input.review.object.spec.ephemeralContainers[_] + not is_exempt(container) + missing_drop_capabilities(container) + msg := sprintf("ephemeral container <%v> is not dropping all required capabilities. Container must drop all of %v or \"ALL\"", [container.name, input.parameters.requiredDropCapabilities]) + } + + + has_disallowed_capabilities(container) { + allowed := {c | c := lower(input.parameters.allowedCapabilities[_])} + not allowed["*"] + capabilities := {c | c := lower(container.securityContext.capabilities.add[_])} + + count(capabilities - allowed) > 0 + } + + missing_drop_capabilities(container) { + must_drop := {c | c := lower(input.parameters.requiredDropCapabilities[_])} + all := {"all"} + dropped := {c | c := lower(container.securityContext.capabilities.drop[_])} + + count(must_drop - dropped) > 0 + count(all - dropped) > 0 + } + + get_default(obj, param, _) := obj[param] + + get_default(obj, param, _default) := _default { + not obj[param] + not obj[param] == false + } + 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) + } ``` From 598df7473390c862e4cf36785b4fc9e6115130da Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Tue, 3 Sep 2024 19:58:47 -0700 Subject: [PATCH 20/20] feat: Update apparmor: add CEL, support securityContext (#533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Update apparmor: add CEL, support securityContext Signed-off-by: Max Smythe * Test no profile Signed-off-by: Max Smythe * fix securitycontext path root for pod in rego Signed-off-by: Max Smythe * update minor version Signed-off-by: Max Smythe * remove metadata var Signed-off-by: Max Smythe * Fix Rego lint Signed-off-by: Max Smythe * Treat each container type separately Signed-off-by: Max Smythe --------- Signed-off-by: Max Smythe Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- .../apparmor/1.1.0/artifacthub-pkg.yml | 22 + .../apparmor/1.1.0/kustomization.yaml | 2 + .../samples/psp-apparmor/constraint.yaml | 12 + .../psp-apparmor/disallowed_ephemeral.yaml | 13 + .../samples/psp-apparmor/example_allowed.yaml | 13 + .../example_allowed_container.yaml | 14 + .../example_allowed_override.yaml | 17 + .../psp-apparmor/example_allowed_pod.yaml | 14 + .../psp-apparmor/example_disallowed.yaml | 13 + .../example_disallowed_no_profile.yaml | 10 + .../example_disallowed_override.yaml | 17 + .../apparmor/1.1.0/suite.yaml | 41 ++ .../apparmor/1.1.0/template.yaml | 218 ++++++++++ .../samples/psp-apparmor/constraint.yaml | 2 +- .../samples/psp-apparmor/example_allowed.yaml | 2 +- .../example_allowed_container.yaml | 14 + .../example_allowed_override.yaml | 17 + .../psp-apparmor/example_allowed_pod.yaml | 14 + .../example_disallowed_no_profile.yaml | 10 + .../example_disallowed_override.yaml | 17 + .../pod-security-policy/apparmor/suite.yaml | 20 + .../apparmor/template.yaml | 231 ++++++++--- .../apparmor/constraint.tmpl | 19 +- src/pod-security-policy/apparmor/src.cel | 83 ++++ src/pod-security-policy/apparmor/src.rego | 48 ++- website/docs/validation/apparmor.md | 377 +++++++++++++++--- 26 files changed, 1129 insertions(+), 131 deletions(-) create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/artifacthub-pkg.yml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/kustomization.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/constraint.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/disallowed_ephemeral.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_container.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_override.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_pod.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_no_profile.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_override.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/suite.yaml create mode 100644 artifacthub/library/pod-security-policy/apparmor/1.1.0/template.yaml create mode 100644 library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_container.yaml create mode 100644 library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_override.yaml create mode 100644 library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_pod.yaml create mode 100644 library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_no_profile.yaml create mode 100644 library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_override.yaml create mode 100644 src/pod-security-policy/apparmor/src.cel diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/artifacthub-pkg.yml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/artifacthub-pkg.yml new file mode 100644 index 0000000000..2212230ed8 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/artifacthub-pkg.yml @@ -0,0 +1,22 @@ +version: 1.1.0 +name: k8spspapparmor +displayName: App Armor +createdAt: "2024-05-29T23:39:01Z" +description: Configures an allow-list of AppArmor profiles for use by containers. This corresponds to specific annotations applied to a PodSecurityPolicy. For information on AppArmor, see https://kubernetes.io/docs/tutorials/clusters/apparmor/ +digest: d77b1285f65085153e6a6e6ac86dc32195591df467c3162abe8cc6c806cab69a +license: Apache-2.0 +homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/apparmor +keywords: + - gatekeeper + - open-policy-agent + - policies +readme: |- + # App Armor + Configures an allow-list of AppArmor profiles for use by containers. This corresponds to specific annotations applied to a PodSecurityPolicy. For information on AppArmor, see https://kubernetes.io/docs/tutorials/clusters/apparmor/ +install: |- + ### Usage + ```shell + kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/pod-security-policy/apparmor/1.1.0/template.yaml + ``` +provider: + name: Gatekeeper Library diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/kustomization.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/kustomization.yaml new file mode 100644 index 0000000000..7d70d11b71 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - template.yaml diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/constraint.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/constraint.yaml new file mode 100644 index 0000000000..cf9b9d3d17 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/constraint.yaml @@ -0,0 +1,12 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sPSPAppArmor +metadata: + name: psp-apparmor +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + parameters: + allowedProfiles: + - localhost/custom diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/disallowed_ephemeral.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/disallowed_ephemeral.yaml new file mode 100644 index 0000000000..cd6fc5f810 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/disallowed_ephemeral.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-disallowed + annotations: + # apparmor.security.beta.kubernetes.io/pod: unconfined # runtime/default + container.apparmor.security.beta.kubernetes.io/nginx: unconfined + labels: + app: nginx-apparmor +spec: + ephemeralContainers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed.yaml new file mode 100644 index 0000000000..204fa00b50 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + annotations: + # apparmor.security.beta.kubernetes.io/pod: unconfined # runtime/default + container.apparmor.security.beta.kubernetes.io/nginx: localhost/custom + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_container.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_container.yaml new file mode 100644 index 0000000000..3d28665d54 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_container.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_override.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_override.yaml new file mode 100644 index 0000000000..1716949850 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_override.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Unconfined" + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_pod.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_pod.yaml new file mode 100644 index 0000000000..6f71d6b3c7 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_allowed_pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed.yaml new file mode 100644 index 0000000000..8ffdaf7689 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-disallowed + annotations: + # apparmor.security.beta.kubernetes.io/pod: unconfined # runtime/default + container.apparmor.security.beta.kubernetes.io/nginx: unconfined + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_no_profile.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_no_profile.yaml new file mode 100644 index 0000000000..4e99cd25dc --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_no_profile.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-disallowed + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_override.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_override.yaml new file mode 100644 index 0000000000..aa4c983018 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/samples/psp-apparmor/example_disallowed_override.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Unconfined" diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/suite.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/suite.yaml new file mode 100644 index 0000000000..861048ba98 --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/suite.yaml @@ -0,0 +1,41 @@ +kind: Suite +apiVersion: test.gatekeeper.sh/v1alpha1 +metadata: + name: apparmor +tests: + - name: apparmor + template: template.yaml + constraint: samples/psp-apparmor/constraint.yaml + cases: + - name: example-allowed + object: samples/psp-apparmor/example_allowed.yaml + assertions: + - violations: no + - name: example-allowed-container + object: samples/psp-apparmor/example_allowed_container.yaml + assertions: + - violations: no + - name: example-allowed-pod + object: samples/psp-apparmor/example_allowed_pod.yaml + assertions: + - violations: no + - name: example-allowed-override + object: samples/psp-apparmor/example_allowed_override.yaml + assertions: + - violations: no + - name: example-disallowed + object: samples/psp-apparmor/example_disallowed.yaml + assertions: + - violations: yes + - name: example-disallowed-override + object: samples/psp-apparmor/example_disallowed_override.yaml + assertions: + - violations: yes + - name: example-disallowed-no-profile + object: samples/psp-apparmor/example_disallowed_no_profile.yaml + assertions: + - violations: yes + - name: disallowed-ephemeral + object: samples/psp-apparmor/disallowed_ephemeral.yaml + assertions: + - violations: yes diff --git a/artifacthub/library/pod-security-policy/apparmor/1.1.0/template.yaml b/artifacthub/library/pod-security-policy/apparmor/1.1.0/template.yaml new file mode 100644 index 0000000000..63aa13f68e --- /dev/null +++ b/artifacthub/library/pod-security-policy/apparmor/1.1.0/template.yaml @@ -0,0 +1,218 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8spspapparmor + annotations: + metadata.gatekeeper.sh/title: "App Armor" + metadata.gatekeeper.sh/version: 1.1.0 + description: >- + Configures an allow-list of AppArmor profiles for use by containers. + This corresponds to specific annotations applied to a PodSecurityPolicy. + For information on AppArmor, see + https://kubernetes.io/docs/tutorials/clusters/apparmor/ +spec: + crd: + spec: + names: + kind: K8sPSPAppArmor + validation: + # Schema for the `parameters` field + openAPIV3Schema: + type: object + description: >- + Configures an allow-list of AppArmor profiles for use by containers. + This corresponds to specific annotations applied to a PodSecurityPolicy. + For information on AppArmor, see + https://kubernetes.io/docs/tutorials/clusters/apparmor/ + 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 + allowedProfiles: + description: "An array of AppArmor profiles. Examples: `runtime/default`, `unconfined`." + 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: podAppArmor + expression: 'has(variables.anyObject.spec.securityContext) && has(variables.anyObject.spec.securityContext.appArmorProfile) ? variables.anyObject.spec.securityContext.appArmorProfile : null' + - name: canonicalPodAppArmor + expression: | + variables.podAppArmor == null ? "runtime/default" : + variables.podAppArmor.type == "RuntimeDefault" ? "runtime/default" : + variables.podAppArmor.type == "Unconfined" ? "unconfined" : + variables.podAppArmor.type == "Localhost" ? "localhost/" + variables.podAppArmor.localhostProfile : "" + # break this mapping up by container type (regular/init/ephemeral) to avoid problems with name collisions, + # which may be a problem when running shift-left (no K8s API server to enforce uniqueness of container names) + - name: appArmorByContainer + expression: | + variables.containers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - name: appArmorByInitContainer + expression: | + variables.initContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - name: appArmorByEphemeralContainer + expression: | + variables.ephemeralContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - 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) + validations: + - expression: | + variables.containers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - expression: | + variables.initContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByInitContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - expression: | + variables.ephemeralContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByEphemeralContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - engine: Rego + source: + rego: | + package k8spspapparmor + + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + container := input_containers[_] + not is_exempt(container) + not input_apparmor_allowed(input.review.object, container) + msg := sprintf("AppArmor profile is not allowed, pod: %v, container: %v. Allowed profiles: %v", [input.review.object.metadata.name, container.name, input.parameters.allowedProfiles]) + } + + input_apparmor_allowed(pod, container) { + get_apparmor_profile(pod, container) == input.parameters.allowedProfiles[_] + } + + 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[_] + } + + get_apparmor_profile(_, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile != null + out := canonicalize_apparmor_profile(profile) + } + + get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + out := pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] + } + + get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + not pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] + out := canonicalize_apparmor_profile(object.get(pod, ["spec", "securityContext", "appArmorProfile"], null)) + } + + canonicalize_apparmor_profile(profile) = out { + profile.type == "RuntimeDefault" + out := "runtime/default" + } + + canonicalize_apparmor_profile(profile) = out { + profile.type == "Unconfined" + out := "unconfined" + } + + canonicalize_apparmor_profile(profile) = out { + profile.type = "Localhost" + out := sprintf("localhost/%s", [profile.localhostProfile]) + } + + canonicalize_apparmor_profile(profile) = out { + profile == null + out := "runtime/default" + } + libs: + - | + 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/apparmor/samples/psp-apparmor/constraint.yaml b/library/pod-security-policy/apparmor/samples/psp-apparmor/constraint.yaml index 7024f1b2ed..cf9b9d3d17 100644 --- a/library/pod-security-policy/apparmor/samples/psp-apparmor/constraint.yaml +++ b/library/pod-security-policy/apparmor/samples/psp-apparmor/constraint.yaml @@ -9,4 +9,4 @@ spec: kinds: ["Pod"] parameters: allowedProfiles: - - runtime/default + - localhost/custom diff --git a/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed.yaml b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed.yaml index 661f2f00dc..204fa00b50 100644 --- a/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed.yaml +++ b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed.yaml @@ -4,7 +4,7 @@ metadata: name: nginx-apparmor-allowed annotations: # apparmor.security.beta.kubernetes.io/pod: unconfined # runtime/default - container.apparmor.security.beta.kubernetes.io/nginx: runtime/default + container.apparmor.security.beta.kubernetes.io/nginx: localhost/custom labels: app: nginx-apparmor spec: diff --git a/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_container.yaml b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_container.yaml new file mode 100644 index 0000000000..3d28665d54 --- /dev/null +++ b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_container.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" diff --git a/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_override.yaml b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_override.yaml new file mode 100644 index 0000000000..1716949850 --- /dev/null +++ b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_override.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Unconfined" + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" diff --git a/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_pod.yaml b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_pod.yaml new file mode 100644 index 0000000000..6f71d6b3c7 --- /dev/null +++ b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + containers: + - name: nginx + image: nginx diff --git a/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_no_profile.yaml b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_no_profile.yaml new file mode 100644 index 0000000000..4e99cd25dc --- /dev/null +++ b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_no_profile.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-disallowed + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx diff --git a/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_override.yaml b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_override.yaml new file mode 100644 index 0000000000..aa4c983018 --- /dev/null +++ b/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_override.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Unconfined" diff --git a/library/pod-security-policy/apparmor/suite.yaml b/library/pod-security-policy/apparmor/suite.yaml index a75ebe1ebe..861048ba98 100644 --- a/library/pod-security-policy/apparmor/suite.yaml +++ b/library/pod-security-policy/apparmor/suite.yaml @@ -11,10 +11,30 @@ tests: object: samples/psp-apparmor/example_allowed.yaml assertions: - violations: no + - name: example-allowed-container + object: samples/psp-apparmor/example_allowed_container.yaml + assertions: + - violations: no + - name: example-allowed-pod + object: samples/psp-apparmor/example_allowed_pod.yaml + assertions: + - violations: no + - name: example-allowed-override + object: samples/psp-apparmor/example_allowed_override.yaml + assertions: + - violations: no - name: example-disallowed object: samples/psp-apparmor/example_disallowed.yaml assertions: - violations: yes + - name: example-disallowed-override + object: samples/psp-apparmor/example_disallowed_override.yaml + assertions: + - violations: yes + - name: example-disallowed-no-profile + object: samples/psp-apparmor/example_disallowed_no_profile.yaml + assertions: + - violations: yes - name: disallowed-ephemeral object: samples/psp-apparmor/disallowed_ephemeral.yaml assertions: diff --git a/library/pod-security-policy/apparmor/template.yaml b/library/pod-security-policy/apparmor/template.yaml index babad1318b..63aa13f68e 100644 --- a/library/pod-security-policy/apparmor/template.yaml +++ b/library/pod-security-policy/apparmor/template.yaml @@ -4,7 +4,7 @@ metadata: name: k8spspapparmor annotations: metadata.gatekeeper.sh/title: "App Armor" - metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Configures an allow-list of AppArmor profiles for use by containers. This corresponds to specific annotations applied to a PodSecurityPolicy. @@ -42,58 +42,177 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | - package k8spspapparmor - - import data.lib.exempt_container.is_exempt - - violation[{"msg": msg, "details": {}}] { - metadata := input.review.object.metadata - container := input_containers[_] - not is_exempt(container) - not input_apparmor_allowed(container, metadata) - msg := sprintf("AppArmor profile is not allowed, pod: %v, container: %v. Allowed profiles: %v", [input.review.object.metadata.name, container.name, input.parameters.allowedProfiles]) - } - - input_apparmor_allowed(container, metadata) { - get_annotation_for(container, metadata) == input.parameters.allowedProfiles[_] - } - - 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[_] - } - - get_annotation_for(container, metadata) = out { - out = metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] - } - get_annotation_for(container, metadata) = out { - not metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] - out = "runtime/default" - } - libs: - - | - 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: podAppArmor + expression: 'has(variables.anyObject.spec.securityContext) && has(variables.anyObject.spec.securityContext.appArmorProfile) ? variables.anyObject.spec.securityContext.appArmorProfile : null' + - name: canonicalPodAppArmor + expression: | + variables.podAppArmor == null ? "runtime/default" : + variables.podAppArmor.type == "RuntimeDefault" ? "runtime/default" : + variables.podAppArmor.type == "Unconfined" ? "unconfined" : + variables.podAppArmor.type == "Localhost" ? "localhost/" + variables.podAppArmor.localhostProfile : "" + # break this mapping up by container type (regular/init/ephemeral) to avoid problems with name collisions, + # which may be a problem when running shift-left (no K8s API server to enforce uniqueness of container names) + - name: appArmorByContainer + expression: | + variables.containers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - name: appArmorByInitContainer + expression: | + variables.initContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - name: appArmorByEphemeralContainer + expression: | + variables.ephemeralContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - 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) + validations: + - expression: | + variables.containers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - expression: | + variables.initContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByInitContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - expression: | + variables.ephemeralContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByEphemeralContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - engine: Rego + source: + rego: | + package k8spspapparmor + + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + container := input_containers[_] + not is_exempt(container) + not input_apparmor_allowed(input.review.object, container) + msg := sprintf("AppArmor profile is not allowed, pod: %v, container: %v. Allowed profiles: %v", [input.review.object.metadata.name, container.name, input.parameters.allowedProfiles]) + } + + input_apparmor_allowed(pod, container) { + get_apparmor_profile(pod, container) == input.parameters.allowedProfiles[_] + } + + 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[_] + } + + get_apparmor_profile(_, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile != null + out := canonicalize_apparmor_profile(profile) + } + + get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + out := pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] + } + + get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + not pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] + out := canonicalize_apparmor_profile(object.get(pod, ["spec", "securityContext", "appArmorProfile"], null)) + } + + canonicalize_apparmor_profile(profile) = out { + profile.type == "RuntimeDefault" + out := "runtime/default" + } + + canonicalize_apparmor_profile(profile) = out { + profile.type == "Unconfined" + out := "unconfined" + } + + canonicalize_apparmor_profile(profile) = out { + profile.type = "Localhost" + out := sprintf("localhost/%s", [profile.localhostProfile]) + } + + canonicalize_apparmor_profile(profile) = out { + profile == null + out := "runtime/default" + } + libs: + - | + 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/src/pod-security-policy/apparmor/constraint.tmpl b/src/pod-security-policy/apparmor/constraint.tmpl index 7951cb5efd..3bf86f42b8 100644 --- a/src/pod-security-policy/apparmor/constraint.tmpl +++ b/src/pod-security-policy/apparmor/constraint.tmpl @@ -4,7 +4,7 @@ metadata: name: k8spspapparmor annotations: metadata.gatekeeper.sh/title: "App Armor" - metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Configures an allow-list of AppArmor profiles for use by containers. This corresponds to specific annotations applied to a PodSecurityPolicy. @@ -42,8 +42,15 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | -{{ file.Read "src/pod-security-policy/apparmor/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }} - libs: - - | -{{ file.Read "src/pod-security-policy/apparmor/lib_exempt_container.rego" | strings.Indent 10 | strings.TrimSuffix "\n" }} + code: + - engine: K8sNativeValidation + source: +{{ file.Read "src/pod-security-policy/apparmor/src.cel" | strings.Indent 10 | strings.TrimSuffix "\n" }} + - engine: Rego + source: + rego: | +{{ file.Read "src/pod-security-policy/apparmor/src.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} + libs: + - | +{{ file.Read "src/pod-security-policy/apparmor/lib_exempt_container.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }} + diff --git a/src/pod-security-policy/apparmor/src.cel b/src/pod-security-policy/apparmor/src.cel new file mode 100644 index 0000000000..25539abcad --- /dev/null +++ b/src/pod-security-policy/apparmor/src.cel @@ -0,0 +1,83 @@ +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: podAppArmor + expression: 'has(variables.anyObject.spec.securityContext) && has(variables.anyObject.spec.securityContext.appArmorProfile) ? variables.anyObject.spec.securityContext.appArmorProfile : null' +- name: canonicalPodAppArmor + expression: | + variables.podAppArmor == null ? "runtime/default" : + variables.podAppArmor.type == "RuntimeDefault" ? "runtime/default" : + variables.podAppArmor.type == "Unconfined" ? "unconfined" : + variables.podAppArmor.type == "Localhost" ? "localhost/" + variables.podAppArmor.localhostProfile : "" +# break this mapping up by container type (regular/init/ephemeral) to avoid problems with name collisions, +# which may be a problem when running shift-left (no K8s API server to enforce uniqueness of container names) +- name: appArmorByContainer + expression: | + variables.containers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) +- name: appArmorByInitContainer + expression: | + variables.initContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) +- name: appArmorByEphemeralContainer + expression: | + variables.ephemeralContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) +- 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) +validations: +- expression: | + variables.containers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' +- expression: | + variables.initContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByInitContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' +- expression: | + variables.ephemeralContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByEphemeralContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' \ No newline at end of file diff --git a/src/pod-security-policy/apparmor/src.rego b/src/pod-security-policy/apparmor/src.rego index 613df8030a..ef4e3fb76e 100644 --- a/src/pod-security-policy/apparmor/src.rego +++ b/src/pod-security-policy/apparmor/src.rego @@ -3,15 +3,14 @@ package k8spspapparmor import data.lib.exempt_container.is_exempt violation[{"msg": msg, "details": {}}] { - metadata := input.review.object.metadata container := input_containers[_] not is_exempt(container) - not input_apparmor_allowed(container, metadata) + not input_apparmor_allowed(input.review.object, container) msg := sprintf("AppArmor profile is not allowed, pod: %v, container: %v. Allowed profiles: %v", [input.review.object.metadata.name, container.name, input.parameters.allowedProfiles]) } -input_apparmor_allowed(container, metadata) { - get_annotation_for(container, metadata) == input.parameters.allowedProfiles[_] +input_apparmor_allowed(pod, container) { + get_apparmor_profile(pod, container) == input.parameters.allowedProfiles[_] } input_containers[c] { @@ -24,10 +23,41 @@ input_containers[c] { c := input.review.object.spec.ephemeralContainers[_] } -get_annotation_for(container, metadata) = out { - out = metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] +get_apparmor_profile(_, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile != null + out := canonicalize_apparmor_profile(profile) } -get_annotation_for(container, metadata) = out { - not metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] - out = "runtime/default" + +get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + out := pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] +} + +get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + not pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] + out := canonicalize_apparmor_profile(object.get(pod, ["spec", "securityContext", "appArmorProfile"], null)) +} + +canonicalize_apparmor_profile(profile) = out { + profile.type == "RuntimeDefault" + out := "runtime/default" +} + +canonicalize_apparmor_profile(profile) = out { + profile.type == "Unconfined" + out := "unconfined" +} + +canonicalize_apparmor_profile(profile) = out { + profile.type = "Localhost" + out := sprintf("localhost/%s", [profile.localhostProfile]) +} + +canonicalize_apparmor_profile(profile) = out { + profile == null + out := "runtime/default" } diff --git a/website/docs/validation/apparmor.md b/website/docs/validation/apparmor.md index c1e2c16d8d..6ed740ddf2 100644 --- a/website/docs/validation/apparmor.md +++ b/website/docs/validation/apparmor.md @@ -16,7 +16,7 @@ metadata: name: k8spspapparmor annotations: metadata.gatekeeper.sh/title: "App Armor" - metadata.gatekeeper.sh/version: 1.0.0 + metadata.gatekeeper.sh/version: 1.1.0 description: >- Configures an allow-list of AppArmor profiles for use by containers. This corresponds to specific annotations applied to a PodSecurityPolicy. @@ -54,61 +54,180 @@ spec: type: string targets: - target: admission.k8s.gatekeeper.sh - rego: | - package k8spspapparmor - - import data.lib.exempt_container.is_exempt - - violation[{"msg": msg, "details": {}}] { - metadata := input.review.object.metadata - container := input_containers[_] - not is_exempt(container) - not input_apparmor_allowed(container, metadata) - msg := sprintf("AppArmor profile is not allowed, pod: %v, container: %v. Allowed profiles: %v", [input.review.object.metadata.name, container.name, input.parameters.allowedProfiles]) - } - - input_apparmor_allowed(container, metadata) { - get_annotation_for(container, metadata) == input.parameters.allowedProfiles[_] - } - - 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[_] - } - - get_annotation_for(container, metadata) = out { - out = metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] - } - get_annotation_for(container, metadata) = out { - not metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] - out = "runtime/default" - } - libs: - - | - 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: podAppArmor + expression: 'has(variables.anyObject.spec.securityContext) && has(variables.anyObject.spec.securityContext.appArmorProfile) ? variables.anyObject.spec.securityContext.appArmorProfile : null' + - name: canonicalPodAppArmor + expression: | + variables.podAppArmor == null ? "runtime/default" : + variables.podAppArmor.type == "RuntimeDefault" ? "runtime/default" : + variables.podAppArmor.type == "Unconfined" ? "unconfined" : + variables.podAppArmor.type == "Localhost" ? "localhost/" + variables.podAppArmor.localhostProfile : "" + # break this mapping up by container type (regular/init/ephemeral) to avoid problems with name collisions, + # which may be a problem when running shift-left (no K8s API server to enforce uniqueness of container names) + - name: appArmorByContainer + expression: | + variables.containers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - name: appArmorByInitContainer + expression: | + variables.initContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - name: appArmorByEphemeralContainer + expression: | + variables.ephemeralContainers.map(container, [container.name, + has(container.securityContext) && has(container.securityContext.appArmorProfile) ? + (container.securityContext.appArmorProfile.type == "RuntimeDefault" ? "runtime/default" : + container.securityContext.appArmorProfile.type == "Unconfined" ? "unconfined" : + container.securityContext.appArmorProfile.type == "Localhost" ? "localhost/" + container.securityContext.appArmorProfile.localhostProfile : "") : + has(variables.anyObject.metadata.annotations) && ("container.apparmor.security.beta.kubernetes.io/" + container.name) in variables.anyObject.metadata.annotations ? + variables.anyObject.metadata.annotations["container.apparmor.security.beta.kubernetes.io/" + container.name] : + variables.canonicalPodAppArmor + ]) + - 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) + validations: + - expression: | + variables.containers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - expression: | + variables.initContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByInitContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - expression: | + variables.ephemeralContainers.all(container, + (container.image in variables.exemptImages) || + variables.appArmorByEphemeralContainer.exists(pair, pair[0] == container.name && pair[1] in variables.params.allowedProfiles) + ) + messageExpression: '"AppArmor profile is not allowed. Allowed Profiles: " + variables.params.allowedProfiles.join(", ")' + - engine: Rego + source: + rego: | + package k8spspapparmor + + import data.lib.exempt_container.is_exempt + + violation[{"msg": msg, "details": {}}] { + container := input_containers[_] + not is_exempt(container) + not input_apparmor_allowed(input.review.object, container) + msg := sprintf("AppArmor profile is not allowed, pod: %v, container: %v. Allowed profiles: %v", [input.review.object.metadata.name, container.name, input.parameters.allowedProfiles]) + } + + input_apparmor_allowed(pod, container) { + get_apparmor_profile(pod, container) == input.parameters.allowedProfiles[_] + } + + 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[_] + } + + get_apparmor_profile(_, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile != null + out := canonicalize_apparmor_profile(profile) + } + + get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + out := pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] + } + + get_apparmor_profile(pod, container) = out { + profile := object.get(container, ["securityContext", "appArmorProfile"], null) + profile == null + not pod.metadata.annotations[sprintf("container.apparmor.security.beta.kubernetes.io/%v", [container.name])] + out := canonicalize_apparmor_profile(object.get(pod, ["spec", "securityContext", "appArmorProfile"], null)) + } + + canonicalize_apparmor_profile(profile) = out { + profile.type == "RuntimeDefault" + out := "runtime/default" + } + + canonicalize_apparmor_profile(profile) = out { + profile.type == "Unconfined" + out := "unconfined" + } + + canonicalize_apparmor_profile(profile) = out { + profile.type = "Localhost" + out := sprintf("localhost/%s", [profile.localhostProfile]) + } + + canonicalize_apparmor_profile(profile) = out { + profile == null + out := "runtime/default" + } + libs: + - | + 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) + } + ``` @@ -135,7 +254,7 @@ spec: kinds: ["Pod"] parameters: allowedProfiles: - - runtime/default + - localhost/custom ``` @@ -157,7 +276,7 @@ metadata: name: nginx-apparmor-allowed annotations: # apparmor.security.beta.kubernetes.io/pod: unconfined # runtime/default - container.apparmor.security.beta.kubernetes.io/nginx: runtime/default + container.apparmor.security.beta.kubernetes.io/nginx: localhost/custom labels: app: nginx-apparmor spec: @@ -173,6 +292,93 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed.yaml ``` + +
+example-allowed-container + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_container.yaml +``` + +
+
+example-allowed-pod + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + containers: + - name: nginx + image: nginx + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_pod.yaml +``` + +
+
+example-allowed-override + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Unconfined" + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/apparmor/samples/psp-apparmor/example_allowed_override.yaml +``` +
example-disallowed @@ -200,6 +406,61 @@ Usage kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed.yaml ``` +
+
+example-disallowed-override + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-allowed + labels: + app: nginx-apparmor +spec: + securityContext: + appArmorProfile: + type: "Localhost" + localhostProfile: "custom" + containers: + - name: nginx + image: nginx + securityContext: + appArmorProfile: + type: "Unconfined" + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_override.yaml +``` + +
+
+example-disallowed-no-profile + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-apparmor-disallowed + labels: + app: nginx-apparmor +spec: + containers: + - name: nginx + image: nginx + +``` + +Usage + +```shell +kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/pod-security-policy/apparmor/samples/psp-apparmor/example_disallowed_no_profile.yaml +``` +
disallowed-ephemeral