Repository: undistro/marvin Branch: main Commit: 8ecf9a6c1f66 Files: 128 Total size: 324.5 KB Directory structure: gitextract_roenwdf7/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yaml ├── .krew.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── checks.md ├── cmd/ │ ├── root.go │ ├── scan.go │ └── version.go ├── examples/ │ ├── labels.yml │ └── replicas.yml ├── go.mod ├── go.sum ├── install.sh ├── internal/ │ └── builtins/ │ ├── cis/ │ │ ├── M-500_workload_in_default_namespace.yaml │ │ └── M-500_workload_in_default_namespace_test.yaml │ ├── embed.go │ ├── embed_test.go │ ├── general/ │ │ ├── M-400_image_tagged_latest.yaml │ │ ├── M-400_image_tagged_latest_test.yaml │ │ ├── M-401_unmanaged_pod.yaml │ │ ├── M-401_unmanaged_pod_test.yaml │ │ ├── M-402_readiness_probe.yaml │ │ ├── M-402_readiness_probe_test.yaml │ │ ├── M-403_liveness_probe.yaml │ │ ├── M-403_liveness_probe_test.yaml │ │ ├── M-404_memory_requests.yaml │ │ ├── M-404_memory_requests_test.yaml │ │ ├── M-405_cpu_requests.yaml │ │ ├── M-405_cpu_requests_test.yaml │ │ ├── M-406_memory_limit.yaml │ │ ├── M-406_memory_limit_test.yaml │ │ ├── M-407_cpu_limit.yaml │ │ ├── M-407_cpu_limit_test.yaml │ │ ├── M-408_sudo_container_entrypoint.yaml │ │ ├── M-408_sudo_container_entrypoint_test.yaml │ │ ├── M-409_deprecated_image_registry.yaml │ │ ├── M-409_deprecated_image_registry_test.yaml │ │ ├── M-410_resource_using_invalid_restartpolicy.yaml │ │ ├── M-410_resource_using_invalid_restartpolicy_test.yaml │ │ ├── M-411_role_binding_referencing_anonymous_or_unauthenticated.yaml │ │ └── M-411_role_binding_referencing_anonymous_or_unauthenticated_test.yaml │ ├── mitre/ │ │ ├── M-200_allowed_registries.yml │ │ ├── M-200_allowed_registries_test.yml │ │ ├── M-201_app_credentials.yml │ │ ├── M-201_app_credentials_test.yml │ │ ├── M-202_auto_mount_service_account_token.yml │ │ ├── M-202_auto_mount_service_account_token_test.yml │ │ ├── M-203_ssh_server.yml │ │ └── M-203_ssh_server_test.yml │ ├── nsa/ │ │ ├── M-300_read_only_root_filesystem.yml │ │ └── M-300_read_only_root_filesystem_test.yml │ └── pss/ │ ├── baseline/ │ │ ├── M-100_host_process.yml │ │ ├── M-100_host_process_test.yml │ │ ├── M-101_host_namespaces.yml │ │ ├── M-101_host_namespaces_test.yml │ │ ├── M-102_privileged_containers.yml │ │ ├── M-102_privileged_containers_test.yml │ │ ├── M-103_capabilities_baseline.yml │ │ ├── M-103_capabilities_baseline_test.yml │ │ ├── M-104_host_path_volumes.yml │ │ ├── M-104_host_path_volumes_test.yml │ │ ├── M-105_host_ports.yml │ │ ├── M-105_host_ports_test.yml │ │ ├── M-106_apparmor.yml │ │ ├── M-106_apparmor_test.yml │ │ ├── M-107_selinux.yml │ │ ├── M-107_selinux_test.yml │ │ ├── M-108_proc_mount.yml │ │ ├── M-108_proc_mount_test.yml │ │ ├── M-109_seccomp_baseline.yml │ │ ├── M-109_seccomp_baseline_test.yml │ │ ├── M-110_sysctls.yml │ │ └── M-110_sysctls_test.yml │ └── restricted/ │ ├── M-111_volume_types.yml │ ├── M-111_volume_types_test.yml │ ├── M-112_privilege_escalation.yml │ ├── M-112_privilege_escalation_test.yml │ ├── M-113_run_as_non_root.yml │ ├── M-113_run_as_non_root_test.yml │ ├── M-114_run_as_user.yml │ ├── M-114_run_as_user_test.yml │ ├── M-115_seccomp_restricted.yml │ ├── M-115_seccomp_restricted_test.yml │ ├── M-116_capabilities_restricted.yml │ └── M-116_capabilities_restricted_test.yml ├── main.go ├── pkg/ │ ├── cmd/ │ │ └── scan.go │ ├── loader/ │ │ ├── builtin.go │ │ ├── builtin_test.go │ │ ├── loader.go │ │ ├── loader_test.go │ │ └── testdata/ │ │ ├── checks/ │ │ │ ├── svc_lb.json │ │ │ └── workloads/ │ │ │ ├── replicas.yaml │ │ │ ├── replicas_test.yaml │ │ │ └── unsupported.txt │ │ └── invalid/ │ │ └── invalid.yml │ ├── printers/ │ │ ├── interface.go │ │ ├── json.go │ │ ├── md.go │ │ ├── table.go │ │ └── yaml.go │ ├── types/ │ │ ├── check.go │ │ ├── check_test.go │ │ ├── report.go │ │ ├── report_test.go │ │ ├── severity.go │ │ ├── severity_test.go │ │ ├── status.go │ │ └── status_test.go │ ├── validator/ │ │ ├── activation.go │ │ ├── compiler.go │ │ ├── compiler_test.go │ │ ├── interface.go │ │ ├── podspec.go │ │ ├── podspec_test.go │ │ └── validator.go │ └── version/ │ ├── version.go │ └── version_test.go └── test/ └── builtins_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly - package-ecosystem: gomod directory: / schedule: interval: weekly ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: push: tags: - 'v*' permissions: contents: write packages: write jobs: release: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: fetch tags run: git fetch --force --tags - name: setup go uses: actions/setup-go@v6 with: go-version-file: 'go.mod' cache: true - name: release id: goreleaser uses: goreleaser/goreleaser-action@v7 with: distribution: goreleaser version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: krew uses: rajatjindal/krew-release-bot@v0.0.50 - name: setup qemu uses: docker/setup-qemu-action@v3 - name: setup docker buildx uses: docker/setup-buildx-action@v3 - name: login uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: metadata id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | type=semver,prefix=v,pattern={{version}} type=semver,prefix=v,pattern={{major}}.{{minor}} - name: create dockerfile.cross run: | sed -e '1 s/\(^FROM\)/FROM --platform=\$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross cat Dockerfile.cross - name: build and push uses: docker/build-push-action@v6 with: build-args: | VERSION=${{ fromJSON(steps.goreleaser.outputs.metadata).version }} COMMIT=${{ fromJSON(steps.goreleaser.outputs.metadata).commit }} DATE=${{ fromJSON(steps.goreleaser.outputs.metadata).date }} context: . platforms: linux/arm64,linux/amd64,linux/s390x,linux/ppc64le file: Dockerfile.cross push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/test.yml ================================================ name: test on: pull_request: branches: [main] push: branches: [main] jobs: validate: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v6 - name: setup go uses: actions/setup-go@v6 with: go-version-file: 'go.mod' cache: true - name: test run: make test - name: build run: make build - name: check license headers run: make checklicense ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib bin # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ .idea dist/ ================================================ FILE: .goreleaser.yaml ================================================ version: 2 before: hooks: - go mod tidy builds: - env: - CGO_ENABLED=0 goos: - linux - windows - darwin ldflags: - >- -s -w -X github.com/undistro/marvin/pkg/version.version={{.Version}} -X github.com/undistro/marvin/pkg/version.commit={{.Commit}} -X github.com/undistro/marvin/pkg/version.date={{.Date}} archives: - formats: [tar.gz] name_template: >- {{ .ProjectName }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }} format_overrides: - goos: windows formats: [zip] checksum: name_template: 'checksums.txt' snapshot: version_template: "{{ incpatch .Version }}-next" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' release: prerelease: auto ================================================ FILE: .krew.yaml ================================================ apiVersion: krew.googlecontainertools.github.com/v1alpha2 kind: Plugin metadata: name: marvin spec: homepage: https://github.com/undistro/marvin shortDescription: Scan clusters with your own checks written in CEL. description: | Marvin scans a Kubernetes cluster by performing CEL expressions to report potential issues, misconfigurations and vulnerabilities. Marvin allows you to write your own checks by using CEL expressions. version: {{ .TagName }} platforms: - selector: matchLabels: os: linux arch: amd64 {{addURIAndSha "https://github.com/undistro/marvin/releases/download/{{ .TagName }}/marvin_Linux_x86_64.tar.gz" .TagName }} bin: marvin - selector: matchLabels: os: linux arch: arm64 {{addURIAndSha "https://github.com/undistro/marvin/releases/download/{{ .TagName }}/marvin_Linux_arm64.tar.gz" .TagName }} bin: marvin - selector: matchLabels: os: darwin arch: amd64 {{addURIAndSha "https://github.com/undistro/marvin/releases/download/{{ .TagName }}/marvin_Darwin_x86_64.tar.gz" .TagName }} bin: marvin - selector: matchLabels: os: darwin arch: arm64 {{addURIAndSha "https://github.com/undistro/marvin/releases/download/{{ .TagName }}/marvin_Darwin_arm64.tar.gz" .TagName }} bin: marvin ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at undistro@getup.io. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Marvin :+1::tada: Thanks for taking the time to contribute! :tada::+1: The following is a set of guidelines for contributing to Marvin, which are hosted in the [Undistro Organization](https://github.com/undistro) on GitHub. ## How Can I Contribute? - **Giving us a star.** It may not seem like much, but it really makes a difference. This is something that everyone can do to help out Marvin. GitHub stars help the project gain visibility and stand out. - **Reviewing the documentation.** Most documentation just needs a review for proper spelling and grammar. If you think a document can be improved in any way, feel free to open a Pull Request with your suggestions. - **Reporting bugs.** We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/undistro/marvin/issues/new/choose). - **Pull Request.** Unless you are fixing a known bug, we **strongly** recommend discussing it with the core team via a GitHub issue before getting started to ensure your work is consistent with Marvin's roadmap and architecture. ================================================ FILE: Dockerfile ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. FROM golang:1.25.4-alpine AS builder ARG TARGETOS ARG TARGETARCH ARG VERSION ARG COMMIT ARG DATE WORKDIR /workspace COPY go.mod go.mod COPY go.sum go.sum RUN go mod download COPY main.go main.go COPY cmd/ cmd/ COPY internal/ internal/ COPY pkg/ pkg/ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build \ -ldflags="-s -w -X github.com/undistro/marvin/pkg/version.version=${VERSION:-docker} \ -X github.com/undistro/marvin/pkg/version.commit=${COMMIT} \ -X github.com/undistro/marvin/pkg/version.date=${DATE}" -a -o marvin main.go FROM alpine:3.22 RUN apk update && apk upgrade && rm -rf /var/cache/apk RUN addgroup -g 8494 -S nonroot && adduser -u 8494 -D -S nonroot -G nonroot USER 8494:8494 WORKDIR / COPY --from=builder /workspace/marvin . ENTRYPOINT ["/marvin"] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MAINTAINERS.md ================================================ # Maintainers Maintainers are approvers who have shown good technical judgement in guiding feature design & development, have displayed overall knowledge of the project and features in the project, and are nurturing and receptive to everyone in the community. The following table lists the Marvin project core maintainers: | Name | GitHub ID | Affiliation | |-------------------------------------------------------------------|--------------------------------------------|----------------------------| | [Matheus Moraes](https://www.linkedin.com/in/matheusfm/) | [@matheusfm](https://github.com/matheusfm) | [Getup](https://getup.io/) | | [Kevin Conner](https://www.linkedin.com/in/kevin-conner-26b5554/) | [@knrc](https://github.com/knrc) | [Getup](https://getup.io/) | ================================================ FILE: Makefile ================================================ # Image URL to use all building/pushing image targets TAG ?= latest IMG ?= ghcr.io/undistro/marvin:${TAG} # Setting SHELL to bash allows bash commands to be executed by recipes. # Options are set to exit when a recipe line exits non-zero or a piped command fails. SHELL = /usr/bin/env bash -o pipefail .SHELLFLAGS = -ec .PHONY: all all: build ##@ General .PHONY: help help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ##@ Development .PHONY: fmt fmt: ## Run go fmt against code. go fmt ./... .PHONY: vet vet: ## Run go vet against code. go vet ./... .PHONY: test test: fmt vet ## Run tests. go test ./... -coverprofile cover.out .PHONY: addlicense addlicense: ## Add copyright license headers in source code files. @test -s $(LOCALBIN)/addlicense || GOBIN=$(LOCALBIN) go install github.com/google/addlicense@latest $(LOCALBIN)/addlicense -c "Undistro Authors" -l "apache" -ignore ".github/**" -ignore ".idea/**" -ignore "dist/**" -ignore ".goreleaser.yaml" -ignore ".krew.yaml" . .PHONY: checklicense checklicense: ## Check copyright license headers in source code files. @test -s $(LOCALBIN)/addlicense || GOBIN=$(LOCALBIN) go install github.com/google/addlicense@latest $(LOCALBIN)/addlicense -c "Undistro Authors" -l "apache" -ignore ".github/**" -ignore ".idea/**" -ignore "dist/**" -ignore ".goreleaser.yaml" -ignore ".krew.yaml" -check . ##@ Build .PHONY: build build: fmt vet ## Build marvin binary. go build -ldflags="-s -w -X github.com/undistro/marvin/pkg/version.version=${TAG}" -o bin/marvin main.go .PHONY: run run: fmt vet ## Run marvin from your host. go run ./main.go PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le .PHONY: docker-buildx docker-buildx: test ## Build and push docker image for cross-platform support. sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - docker buildx create --name cross-builder docker buildx use cross-builder - docker buildx build --push --platform=$(PLATFORMS) --build-arg VERSION=${TAG} --tag ${IMG} -f Dockerfile.cross . - docker buildx rm cross-builder rm Dockerfile.cross .PHONY: docker-build docker-build: test ## Build docker image. docker build --build-arg VERSION=${TAG} -t ${IMG} . .PHONY: docker-push docker-push: ## Push docker image. docker push ${IMG} ## Location to install dependencies to LOCALBIN ?= $(shell pwd)/bin $(LOCALBIN): mkdir -p $(LOCALBIN) ================================================ FILE: README.md ================================================
Marvin logo [![Go Reference](https://pkg.go.dev/badge/github.com/undistro/marvin.svg)](https://pkg.go.dev/github.com/undistro/marvin) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/marvin)](https://artifacthub.io/packages/krew/krew-index/marvin) [![Test](https://github.com/undistro/marvin/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/undistro/marvin/actions/workflows/test.yml) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/undistro/marvin?sort=semver&color=brightgreen) ![GitHub](https://img.shields.io/github/license/undistro/marvin?color=brightgreen) [![Go Report Card](https://goreportcard.com/badge/github.com/undistro/marvin)](https://goreportcard.com/report/github.com/undistro/marvin) ![GitHub all releases](https://img.shields.io/github/downloads/undistro/marvin/total)
Marvin is a CLI tool designed to help Kubernetes cluster administrators ensure the security and reliability of their environments. Using a comprehensive set of [CEL (Common Expression Language)](https://github.com/google/cel-spec) expressions, Marvin performs extensive checks on cluster resources, identifying potential issues, misconfigurations, and vulnerabilities that could pose a risk to the system. It helps ensure that your Kubernetes clusters are always in compliance with best practices and industry standards. Marvin is also used as a plugin in [Zora](https://zora-docs.undistro.io/latest/). * [Installation](#installation) * [Manually](#manually) * [Install via script](#install-via-script) * [Install via Krew](#install-via-krew) * [Install from source](#install-from-source) * [Usage](#usage) * [Built-in checks](#built-in-checks) * [Custom checks](#custom-checks) * [Skipping resources](#skipping-resources) * [RBAC](#rbac) * [Contributing](#contributing) * [License](#license) _Please [star :star:](https://github.com/undistro/marvin/stargazers) the repo if you want us to continue developing and improving Marvin!_ :grin: # Installation The pre-compiled binaries are available in [GitHub releases page](https://github.com/undistro/marvin/releases) and can be installed manually, via script or as a `kubectl` plugin with [Krew](https://krew.sigs.k8s.io). ## Manually 1. Download the file for your system/architecture from the [GitHub releases page](https://github.com/undistro/marvin/releases) 2. Unpack the downloaded archive (e.g `tar -xzf marvin_Linux_x86_64.tar.gz`) 3. Make sure the binary has execution bit turned on (`chmod +x ./marvin`) 4. Move the binary somewhere in your `$PATH` (e.g `sudo mv ./marvin /usr/local/bin/`) ## Install via script The process above can be automated by the following script: ```shell curl -sSfL https://raw.githubusercontent.com/undistro/marvin/main/install.sh | sh -s -- -b $HOME/.local/bin ``` ## Install via [Krew](https://krew.sigs.k8s.io) You can install Marvin as a `kubectl` plugin via [Krew](https://krew.sigs.k8s.io): ```shell kubectl krew install marvin ``` Then you can use Marvin with `kubectl` prefix: ```shell kubectl marvin version ``` ## Install from source ```shell go install github.com/undistro/marvin@latest ``` # Usage ## Built-in checks Scan the current-context Kubernetes cluster performing the [built-in checks](internal/builtins): ```shell marvin scan ``` ``` SEVERITY ID CHECK STATUS FAILED PASSED SKIPPED High M-101 Host namespaces Failed 8 25 0 High M-104 HostPath volume Failed 8 25 0 High M-201 Application credentials stored in configuration files Failed 2 45 0 High M-102 Privileged container Failed 2 31 0 High M-103 Insecure capabilities Failed 2 31 0 High M-100 Privileged access to the Windows node Passed 0 33 0 High M-105 Not allowed hostPort Passed 0 33 0 Medium M-113 Container could be running as root user Failed 33 0 0 Medium M-407 CPU not limited Failed 31 2 0 Medium M-406 Memory not limited Failed 27 6 0 Medium M-404 Memory requests not specified Failed 26 7 0 Medium M-402 Readiness and startup probe not configured Failed 25 8 0 Medium M-403 Liveness probe not configured Failed 25 8 0 Medium M-405 CPU requests not specified Failed 23 10 0 Medium M-106 Forbidden AppArmor profile Passed 0 33 0 Medium M-107 Forbidden SELinux options Passed 0 33 0 Medium M-108 Forbidden proc mount type Passed 0 33 0 Medium M-109 Forbidden seccomp profile Passed 0 33 0 Medium M-110 Unsafe sysctls Passed 0 33 0 Medium M-112 Allowed privilege escalation Passed 0 33 0 Medium M-114 Container running as root UID Passed 0 33 0 Medium M-200 Image registry not allowed Passed 0 33 0 Medium M-400 Image tagged latest Passed 0 33 0 Medium M-408 Sudo in container entrypoint Passed 0 33 0 Medium M-409 Deprecated image registry Passed 0 33 0 Medium M-500 Workload in default namespace Passed 0 33 0 Medium M-410 Not allowed restartPolicy Passed 0 18 0 Low M-116 Not allowed added/dropped capabilities Failed 33 0 0 Low M-202 Automounted service account token Failed 33 0 0 Low M-115 Not allowed seccomp profile Failed 29 4 0 Low M-300 Root filesystem write allowed Failed 29 4 0 Low M-111 Not allowed volume type Failed 8 25 0 Low M-203 SSH server running inside container Passed 0 39 0 Low M-401 Unmanaged Pod Passed 0 15 0 ``` The default output format is `table` which represents a summary of checks result. You can provide `json` or `yaml` in the `-o/--output` flag to get more details. Run `marvin scan --help` to see all available options. ## Custom checks Marvin allows you to write your own checks by using [CEL expressions](https://github.com/google/cel-spec) in a YAML file like the example below. ```yaml id: CUSTOM-001 severity: Medium message: "Replicas limit" match: resources: - group: apps version: v1 resource: deployments validations: - expression: > object.spec.replicas <= 5 message: "Deployment with more than 5 replicas" ``` If an expression evaluates to `false`, the check fails. This is how built-in Marvin checks are defined as well. You can see all the built-in checks in the [`internal/builtins` folder](internal/builtins) for examples. If you want to quickly test CEL expressions from your browser, check out the [CEL Playground](https://playcel.undistro.io/). Then provide the directory path with your custom check files in the `-f/--checks` flag: ```shell marvin scan --disable-builtin --checks ./examples/ ``` ``` SEVERITY ID CHECK STATUS FAILED PASSED SKIPPED Medium CUSTOM-001 Replicas limit Passed 0 2 0 ``` The flag `--disable-builtin` disables the built-in Marvin checks. If the check matches a PodSpec (`Pod`, `ReplicationController`, `ReplicaSet`, `Deployment`, `StatefulSet`, `DaemonSet`, `Job` or `CronJob`) the `podSpec` and `allContainers` inputs are available for expressions. The `allContainers` input is a list of all containers including `initContainers` and `ephemeralContainers`. ## Skipping resources You can use annotations to skip certain checks for specific resources in your cluster. By adding the `marvin.undistro.io/skip` annotation to a resource, you can specify a comma-separated list of check IDs to skip. Example: ```shell kubectl annotate deployment nginx marvin.undistro.io/skip='M-202, M-111' ``` By default, Marvin will respect the `marvin.undistro.io/skip` annotation when performing checks. However, you can disable this behavior by using the `--disable-annotation-skip` flag. This flag will cause Marvin to perform all checks on all resources. If you prefer to use a different annotation to skip checks, you can use the `--skip-annotation` flag to specify the annotation name. Example: `--skip-annotation='my-company.com/skip-checks'` ## RBAC Currently, the built-in checks look for the below resources and Marvin needs view (`get` and `list`) permission to verify them. - `v1/pods` - `v1/configmaps` - `v1/services` - `apps/v1/deployments` - `apps/v1/daemonsets` - `apps/v1/statefulsets` - `apps/v1/replicasets` - `batch/v1/cronjobs` - `batch/v1/jobs`
Here is a sample `ClusterRole` for Marvin: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: marvin rules: - apiGroups: [ "" ] resources: - configmaps - pods - services verbs: [ "get", "list" ] - apiGroups: [ "apps" ] resources: - daemonsets - deployments - statefulsets - replicasets verbs: [ "get", "list" ] - apiGroups: [ batch ] resources: - jobs - cronjobs verbs: [ "get", "list" ] ```
> **Note** > You can write a custom check to look at any resource. > But Marvin needs view permission. > Remember to update RBAC for new resources you want to check. # Contributing We appreciate your contribution. Please refer to our [contributing guideline](https://github.com/undistro/marvin/blob/main/CONTRIBUTING.md) for further information. This project adheres to the Contributor Covenant [code of conduct](https://github.com/undistro/marvin/blob/main/CODE_OF_CONDUCT.md). # License Marvin is available under the Apache 2.0 license. See the [LICENSE](LICENSE) file for more info. ================================================ FILE: checks.md ================================================ # Checks Overview In the table below, you can view all checks present on Marvin. Click on the #ID column item for more details about each check. | Framework | #ID | Severity | Message | |------------------|------------------------------------------------------------------------------------------------------|----------|------------------------------------------------------------------| | CIS Benchmarks | [M-500](/internal/builtins/cis/M-500_default_namespace.yaml) | Medium | Workloads in default namespace | | General | [M-400](/internal/builtins/general/M-400_image_tag_latest.yaml) | Medium | Image tagged latest | | | [M-401](/internal/builtins/general/M-401_unmanaged_pod.yaml) | Low | Unmanaged Pod | | | [M-402](/internal/builtins/general/M-402_readiness_probe.yaml) | Medium | Readiness and startup probe not configured | | | [M-403](/internal/builtins/general/M-403_liveness_probe.yaml) | Medium | Liveness probe not configured | | | [M-404](/internal/builtins/general/M-404_memory_requests.yaml) | Medium | Memory requests not specified | | | [M-405](/internal/builtins/general/M-405_cpu_requests.yaml) | Medium | CPU requests not specified | | | [M-406](/internal/builtins/general/M-406_memory_limit.yaml) | Medium | Memory not limited | | | [M-407](/internal/builtins/general/M-407_cpu_limit.yaml) | Medium | CPU not limited | | | [M-408](/internal/builtins/general/M-408_sudo_container_entrypoint.yaml) | Medium | Sudo in container entrypoint | | | [M-409](/internal/builtins/general/M-409_deprecated_image_registry.yaml) | Medium | Deprecated image registry | | | [M-410](/internal/builtins/general/M-410_resource_using_invalid_restartpolicy.yaml) | Medium | Resource is using an invalid restartPolicy | | | [M-411](/internal/builtins/general/M-411_role_binding_referencing_anonymous_or_unauthanticated.yaml) | Medium | Role Binding referencing anonymous user or unauthenticated group | | NSA-CISA | [M-300](/internal/builtins/nsa/M-300_read_only_root_filesystem.yml) | Low | Root filesystem write allowed | | MITRE ATT&CK | [M-200](/internal/builtins/mitre/M-200_allowed_registries.yml) | Medium | Image registry not allowed | | | [M-201](/internal/builtins/mitre/M-201_app_credentials.yml) | High | Application credentials stored in configuration files | | | [M-202](/internal/builtins/mitre/M-202_auto_mount_service_account.yml) | Low | Automounted service account token | | | [M-203](/internal/builtins/mitre/M-203_ssh.yml) | Low | SSH server running inside container | | PSS - Baseline | [M-100](/internal/builtins/pss/baseline/M-100_host_process.yml) | High | Privileged access to the Windows node | | | [M-101](/internal/builtins/pss/baseline/M-101_host_namespaces.yml) | High | Host namespaces | | | [M-102](/internal/builtins/pss/baseline/M-102_privileged_containers.yml) | High | Privileged container | | | [M-103](/internal/builtins/pss/baseline/M-103_capabilities.yml) | High | Insecure capabilities | | | [M-104](/internal/builtins/pss/baseline/M-104_host_path_volumes.yml) | High | HostPath volume | | | [M-105](/internal/builtins/pss/baseline/M-105_host_ports.yml) | High | Not allowed hostPort | | | [M-106](/internal/builtins/pss/baseline/M-106_apparmor.yml) | Medium | Forbidden AppArmor profile | | | [M-107](/internal/builtins/pss/baseline/M-107_selinux.yml) | Medium | Forbidden SELinux options | | | [M-108](/internal/builtins/pss/baseline/M-108_proc_mount.yml) | Medium | Forbidden proc mount type | | | [M-109](/internal/builtins/pss/baseline/M-109_seccomp.yml) | Medium | Forbidden seccomp profile | | | [M-110](/internal/builtins/pss/baseline/M-110_sysctls.yml) | Medium | Unsafe sysctls | | PSS - Restricted | [M-111](/internal/builtins/pss/restricted/M-111_volume_types.yml) | Low | Not allowed volume type | | | [M-112](/internal/builtins/pss/restricted/M-112_privilege_escalation.yml) | Medium | Allowed privilege escalation | | | [M-113](/internal/builtins/pss/restricted/M-113_run_as_non_root.yml) | Medium | Container could be running as root user | | | [M-114](/internal/builtins/pss/restricted/M-114_run_as_user.yml) | Medium | Container running as root UID | | | [M-115](/internal/builtins/pss/restricted/M-115_seccomp.yml) | Low | Not allowed seccomp profile | | | [M-116](/internal/builtins/pss/restricted/M-116_capabilities.yml) | Low | Not allowed added/dropped capabilities | ================================================ FILE: cmd/root.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "flag" "os" "path/filepath" "strings" "github.com/fatih/color" "github.com/go-logr/logr" "github.com/spf13/cobra" "k8s.io/klog/v2" "k8s.io/klog/v2/klogr" ) var noColor bool // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "marvin", Short: "A Kubernetes cluster scanner", } func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) } } func isKubectlPlugin() bool { return strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-") } func execName() string { n := "marvin" if isKubectlPlugin() { return "kubectl " + n } return n } func init() { cobra.OnInitialize(initNoColor) if isKubectlPlugin() { usageTpl := strings.NewReplacer("{{.UseLine}}", "kubectl {{.UseLine}}", "{{.CommandPath}}", "kubectl {{.CommandPath}}").Replace(rootCmd.UsageTemplate()) rootCmd.SetUsageTemplate(usageTpl) } rootCmd.PersistentFlags().BoolVar(&noColor, "no-color", false, "Disable color output") var allFlags flag.FlagSet klog.InitFlags(&allFlags) allFlags.VisitAll(func(f *flag.Flag) { if f.Name == "v" { rootCmd.PersistentFlags().AddGoFlag(f) } }) rootCmd.SetContext(logr.NewContext(context.Background(), klogr.New())) } func initNoColor() { color.NoColor = noColor } ================================================ FILE: cmd/scan.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "os" "github.com/spf13/cobra" "github.com/undistro/marvin/pkg/cmd" ) var ( scanOptions = cmd.NewScanOptions() // scanCmd represents the scan command scanCmd = &cobra.Command{ Use: "scan [flags]", Short: "Scan a Kubernetes cluster", Example: fmt.Sprintf(` # Scan the current cluster %[1]s scan # Scan the 'foo' namespace of the current cluster %[1]s scan -n foo # Scan the current cluster providing custom checks %[1]s scan --checks ./examples/ # Scan the current cluster providing custom checks and disabling the built-in checks %[1]s scan --disable-builtin --checks ./examples/ # Scan a specific cluster using a kubeconfig file %[1]s scan --kubeconfig /path/to/kubeconfig.yml # Scan the current cluster, but do not fail even if there are errors in the report %[1]s scan --no-fail # Scan the current cluster and generate output in JSON format %[1]s scan -o json`, execName()), RunE: func(c *cobra.Command, args []string) error { if err := scanOptions.Init(c.Context()); err != nil { return err } hasError, err := scanOptions.Run() if err != nil { return err } if hasError && !*scanOptions.NoFail { os.Exit(2) } return nil }, } ) func init() { rootCmd.AddCommand(scanCmd) scanOptions.AddFlags(scanCmd.Flags()) } ================================================ FILE: cmd/version.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "encoding/json" "fmt" "github.com/spf13/cobra" "sigs.k8s.io/yaml" "github.com/undistro/marvin/pkg/version" ) var ( versionOutput string // versionCmd represents the version command versionCmd = &cobra.Command{ Use: "version", Short: "Show the version of Marvin", RunE: func(c *cobra.Command, args []string) error { v := version.Get() var s string switch versionOutput { case "json": b, err := json.MarshalIndent(&v, "", " ") if err != nil { return err } s = string(b) case "yaml": b, err := yaml.Marshal(&v) if err != nil { return err } s = string(b) default: s = v.String() } fmt.Println(s) return nil }, } ) func init() { rootCmd.AddCommand(versionCmd) versionCmd.Flags().StringVarP(&versionOutput, "output", "o", "", `Output format. One of: ("json", "yaml")`) } ================================================ FILE: examples/labels.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: CUSTOM-002 severity: Low message: "Required labels" match: resources: - group: "" version: v1 resource: pods params: requiredLabels: - app validations: - expression: > has(object.metadata.labels) && params.requiredLabels.all(req, req in object.metadata.labels ) message: "Pod without required labels" ================================================ FILE: examples/replicas.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: CUSTOM-001 severity: Medium message: "Replicas limit" match: resources: - group: apps version: v1 resource: deployments validations: - expression: > object.spec.replicas <= 5 message: "Deployment with more than 5 replicas" ================================================ FILE: go.mod ================================================ module github.com/undistro/marvin go 1.25.4 require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/fatih/color v1.18.0 github.com/go-logr/logr v1.4.3 github.com/google/cel-go v0.26.0 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 k8s.io/api v0.34.2 k8s.io/apimachinery v0.34.2 k8s.io/apiserver v0.34.2 k8s.io/cli-runtime v0.34.2 k8s.io/client-go v0.34.2 k8s.io/klog/v2 v2.130.1 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/yaml v1.6.0 ) require ( cel.dev/expr v0.24.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-base v0.34.2 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) ================================================ FILE: go.sum ================================================ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= k8s.io/cli-runtime v0.34.2 h1:cct1GEuWc3IyVT8MSCoIWzRGw9HJ/C5rgP32H60H6aE= k8s.io/cli-runtime v0.34.2/go.mod h1:X13tsrYexYUCIq8MarCBy8lrm0k0weFPTpcaNo7lms4= k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= ================================================ FILE: install.sh ================================================ #!/bin/sh set -e # Code generated by godownloader on 2023-02-28T17:14:26Z. DO NOT EDIT. # usage() { this=$1 cat </dev/null } echoerr() { echo "$@" 1>&2 } log_prefix() { echo "$0" } _logp=6 log_set_priority() { _logp="$1" } log_priority() { if test -z "$1"; then echo "$_logp" return fi [ "$1" -le "$_logp" ] } log_tag() { case $1 in 0) echo "emerg" ;; 1) echo "alert" ;; 2) echo "crit" ;; 3) echo "err" ;; 4) echo "warning" ;; 5) echo "notice" ;; 6) echo "info" ;; 7) echo "debug" ;; *) echo "$1" ;; esac } log_debug() { log_priority 7 || return 0 echoerr "$(log_prefix)" "$(log_tag 7)" "$@" } log_info() { log_priority 6 || return 0 echoerr "$(log_prefix)" "$(log_tag 6)" "$@" } log_err() { log_priority 3 || return 0 echoerr "$(log_prefix)" "$(log_tag 3)" "$@" } log_crit() { log_priority 2 || return 0 echoerr "$(log_prefix)" "$(log_tag 2)" "$@" } uname_os() { os=$(uname -s | tr '[:upper:]' '[:lower:]') case "$os" in cygwin_nt*) os="windows" ;; mingw*) os="windows" ;; msys_nt*) os="windows" ;; esac echo "$os" } uname_arch() { arch=$(uname -m) case $arch in x86_64) arch="amd64" ;; x86) arch="386" ;; i686) arch="386" ;; i386) arch="386" ;; aarch64) arch="arm64" ;; armv5*) arch="armv5" ;; armv6*) arch="armv6" ;; armv7*) arch="armv7" ;; esac echo ${arch} } uname_os_check() { os=$(uname_os) case "$os" in darwin) return 0 ;; dragonfly) return 0 ;; freebsd) return 0 ;; linux) return 0 ;; android) return 0 ;; nacl) return 0 ;; netbsd) return 0 ;; openbsd) return 0 ;; plan9) return 0 ;; solaris) return 0 ;; windows) return 0 ;; esac log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" return 1 } uname_arch_check() { arch=$(uname_arch) case "$arch" in 386) return 0 ;; amd64) return 0 ;; arm64) return 0 ;; armv5) return 0 ;; armv6) return 0 ;; armv7) return 0 ;; ppc64) return 0 ;; ppc64le) return 0 ;; mips) return 0 ;; mipsle) return 0 ;; mips64) return 0 ;; mips64le) return 0 ;; s390x) return 0 ;; amd64p32) return 0 ;; esac log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" return 1 } untar() { tarball=$1 case "${tarball}" in *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; *.tar) tar --no-same-owner -xf "${tarball}" ;; *.zip) unzip "${tarball}" ;; *) log_err "untar unknown archive format for ${tarball}" return 1 ;; esac } http_download_curl() { local_file=$1 source_url=$2 header=$3 if [ -z "$header" ]; then code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") else code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") fi if [ "$code" != "200" ]; then log_debug "http_download_curl received HTTP status $code" return 1 fi return 0 } http_download_wget() { local_file=$1 source_url=$2 header=$3 if [ -z "$header" ]; then wget -q -O "$local_file" "$source_url" else wget -q --header "$header" -O "$local_file" "$source_url" fi } http_download() { log_debug "http_download $2" if is_command curl; then http_download_curl "$@" return elif is_command wget; then http_download_wget "$@" return fi log_crit "http_download unable to find wget or curl" return 1 } http_copy() { tmp=$(mktemp) http_download "${tmp}" "$1" "$2" || return 1 body=$(cat "$tmp") rm -f "${tmp}" echo "$body" } github_release() { owner_repo=$1 version=$2 test -z "$version" && version="latest" giturl="https://github.com/${owner_repo}/releases/${version}" json=$(http_copy "$giturl" "Accept:application/json") test -z "$json" && return 1 version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') test -z "$version" && return 1 echo "$version" } hash_sha256() { TARGET=${1:-/dev/stdin} if is_command gsha256sum; then hash=$(gsha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command sha256sum; then hash=$(sha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command shasum; then hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command openssl; then hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f a else log_crit "hash_sha256 unable to find command to compute sha-256 hash" return 1 fi } hash_sha256_verify() { TARGET=$1 checksums=$2 if [ -z "$checksums" ]; then log_err "hash_sha256_verify checksum file not specified in arg2" return 1 fi BASENAME=${TARGET##*/} want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) if [ -z "$want" ]; then log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" return 1 fi got=$(hash_sha256 "$TARGET") if [ "$want" != "$got" ]; then log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" return 1 fi } cat /dev/null < allContainers.all(container, container.image.contains(":") && // image digest contains ":" [container.image.substring(container.image.lastIndexOf(":")+1)].all(image, !image.contains("/") && !(image in ["latest", ""]) ) ) ================================================ FILE: internal/builtins/general/M-400_image_tagged_latest_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "untagged" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "registry + untagged image" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: registry.com/nginx - name: "registry + port + untagged image" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: registry.com:10443/nginx - name: "latest tag" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:latest - name: "registry + image tagged latest" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: registry.com/nginx:latest - name: "registry + port + image tagged latest" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: registry.com:10443/nginx:latest - name: "all ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - image: nginx:1 name: cont1 - image: nginx:1.25 name: cont2 - image: nginx@sha256:593dac25b7733ffb7afe1a72649a43e574778bf025ad60514ef40f6b5d606247 name: cont3 - image: registry.com/images/nginx:1 name: cont4 - image: registry.com:10443/nginx:1.25 name: cont5 - image: registry.com/nginx@sha256:593dac25b7733ffb7afe1a72649a43e574778bf025ad60514ef40f6b5d606247 name: cont6 ================================================ FILE: internal/builtins/general/M-401_unmanaged_pod.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-401 slug: unmanaged-pod severity: Low message: "Unmanaged Pod" match: resources: - group: "" version: v1 resource: pods variables: - name: owners expression: object.metadata.?ownerReferences.orValue([]) validations: - expression: > variables.owners != null && variables.owners.exists(o, o.?controller.orValue(false) == true) ================================================ FILE: internal/builtins/general/M-401_unmanaged_pod_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx-7f7b76bc5b-6vb88 ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: true kind: ReplicaSet name: nginx-7f7b76bc5b uid: daf540b6-b932-4b2d-a9b1-fa9cdfbff38b labels: app: nginx spec: containers: - name: nginx image: nginx - name: "controller false" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx-7f7b76bc5b-6vb88 ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: false kind: ReplicaSet name: nginx-7f7b76bc5b uid: daf540b6-b932-4b2d-a9b1-fa9cdfbff38b labels: app: nginx spec: containers: - name: nginx image: nginx - name: "unmanaged" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "ownerReferences set to null" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx ownerReferences: null labels: app: nginx spec: containers: - name: nginx image: nginx - name: "ownerReferences empty" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx ownerReferences: [] labels: app: nginx spec: containers: - name: nginx image: nginx ================================================ FILE: internal/builtins/general/M-402_readiness_probe.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-402 slug: readiness-probe severity: Medium message: "Readiness and startup probe not configured" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets variables: - name: owners expression: object.metadata.?ownerReferences.orValue([]) validations: - expression: > ( object.kind == "Pod" && variables.owners != null && variables.owners.exists(o, o.?kind.orValue("") == "Job" && o.?apiVersion.orValue("") == "batch/v1") ) || podSpec.containers.all(c, has(c.readinessProbe) || has(c.startupProbe)) ================================================ FILE: internal/builtins/general/M-402_readiness_probe_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "readinessProbe not specified" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "readinessProbe not specified in managed pod" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx-7f7b76bc5b-6vb88 ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: true kind: ReplicaSet name: nginx-7f7b76bc5b uid: daf540b6-b932-4b2d-a9b1-fa9cdfbff38b labels: app: nginx spec: containers: - name: nginx image: nginx - name: "readinessProbe set to null" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx readinessProbe: null - name: "readiness probe configured" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx readinessProbe: httpGet: port: 80 - name: "startup probe configured" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx startupProbe: httpGet: port: 80 - name: "managed pod with readiness probe configured" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx-7f7b76bc5b-6vb88 ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: true kind: ReplicaSet name: nginx-7f7b76bc5b uid: daf540b6-b932-4b2d-a9b1-fa9cdfbff38b labels: app: nginx spec: containers: - name: nginx image: nginx readinessProbe: httpGet: port: 80 - name: "managed pod with startup probe configured" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx-7f7b76bc5b-6vb88 ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: true kind: ReplicaSet name: nginx-7f7b76bc5b uid: daf540b6-b932-4b2d-a9b1-fa9cdfbff38b labels: app: nginx spec: containers: - name: nginx image: nginx startupProbe: httpGet: port: 80 - name: "job" pass: true input: | apiVersion: v1 kind: Pod metadata: name: job-28119672-9sv4f labels: app: job ownerReferences: - apiVersion: batch/v1 blockOwnerDeletion: true controller: true kind: Job name: job-28119672 uid: 3c506232-bf9b-475b-add5-d5e850ba6ceb spec: containers: - name: job image: job - name: "ownerReferences set to null" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx ownerReferences: null labels: app: nginx spec: containers: - name: nginx image: nginx ================================================ FILE: internal/builtins/general/M-403_liveness_probe.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-403 slug: liveness-probe severity: Medium message: "Liveness probe not configured" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets variables: - name: owners expression: object.metadata.?ownerReferences.orValue([]) validations: - expression: > ( object.kind == "Pod" && variables.owners != null && variables.owners.exists(o, o.?kind.orValue("") == "Job" && o.?apiVersion.orValue("") == "batch/v1") ) || podSpec.containers.all(c, has(c.livenessProbe)) ================================================ FILE: internal/builtins/general/M-403_liveness_probe_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "livenessProbe not specified" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "livenessProbe not specified in managed pod" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx-7f7b76bc5b-6vb88 ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: true kind: ReplicaSet name: nginx-7f7b76bc5b uid: daf540b6-b932-4b2d-a9b1-fa9cdfbff38b labels: app: nginx spec: containers: - name: nginx image: nginx - name: "livenessProbe set to null" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx livenessProbe: null - name: "ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx livenessProbe: httpGet: port: 80 - name: "managed pod ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx-7f7b76bc5b-6vb88 ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: true kind: ReplicaSet name: nginx-7f7b76bc5b uid: daf540b6-b932-4b2d-a9b1-fa9cdfbff38b labels: app: nginx spec: containers: - name: nginx image: nginx livenessProbe: httpGet: port: 80 - name: "job" pass: true input: | apiVersion: v1 kind: Pod metadata: name: job-28119672-9sv4f labels: app: job ownerReferences: - apiVersion: batch/v1 blockOwnerDeletion: true controller: true kind: Job name: job-28119672 uid: 3c506232-bf9b-475b-add5-d5e850ba6ceb spec: containers: - name: job image: job ================================================ FILE: internal/builtins/general/M-404_memory_requests.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-404 slug: memory-requests severity: Medium message: "Memory requests not specified" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?resources.?requests.?memory.orValue("") != "") ================================================ FILE: internal/builtins/general/M-404_memory_requests_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "resources not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "resources requests not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: memory: 128Mi - name: "memory requests not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: requests: cpu: 10m - name: "ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: requests: cpu: 10m memory: 64Mi ================================================ FILE: internal/builtins/general/M-405_cpu_requests.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-405 slug: cpu-requests severity: Medium message: "CPU requests not specified" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?resources.?requests.?cpu.orValue("") != "") ================================================ FILE: internal/builtins/general/M-405_cpu_requests_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "resources not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "resources requests not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: memory: 128Mi - name: "cpu requests not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: requests: memory: 64Mi - name: "ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: requests: cpu: 10m memory: 64Mi ================================================ FILE: internal/builtins/general/M-406_memory_limit.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-406 slug: memory-limit severity: Medium message: "Memory not limited" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?resources.?limits.?memory.orValue("") != "") ================================================ FILE: internal/builtins/general/M-406_memory_limit_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "resources not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "resources limits not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: requests: memory: 64Mi - name: "memory limits not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: cpu: 500m - name: "ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: memory: 128Mi ================================================ FILE: internal/builtins/general/M-407_cpu_limit.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-407 slug: cpu-limit severity: Medium message: "CPU not limited" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?resources.?limits.?cpu.orValue("") != "") ================================================ FILE: internal/builtins/general/M-407_cpu_limit_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "resources not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "resources limits not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: requests: cpu: 5m - name: "CPU limits not set" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: memory: 128Mi - name: "ok" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: cpu: 500m ================================================ FILE: internal/builtins/general/M-408_sudo_container_entrypoint.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-408 slug: sudo-container-entrypoint severity: Medium message: "Sudo in container entrypoint" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?command.orValue([]).all(cmd, !cmd.contains("sudo")) ) ================================================ FILE: internal/builtins/general/M-408_sudo_container_entrypoint_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "command not set" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "command without sudo" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx command: ["printenv"] resources: requests: cpu: 5m - name: "command with sudo on entrypoint" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx command: ["sudo printenv"] resources: limits: memory: 128Mi ================================================ FILE: internal/builtins/general/M-409_deprecated_image_registry.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-409 slug: deprecated-image-registry severity: Medium message: "Deprecated image registry" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, !c.image.contains("k8s.grc.io")) ================================================ FILE: internal/builtins/general/M-409_deprecated_image_registry_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "registry not defined" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx - name: "registry deprecated" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: reg-fail image: k8s.grc.io/pause - name: "registry deprecated top" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: reg-fail image: k8s.grc.io/pause - name: reg-ok1 image: ok-registry.com:80/nginx@sha256:asdf - name: reg-ok2 image: also-ok-registry.com:80/nginx@sha256:asdf - name: "registry deprecated middle" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: reg-ok1 image: ok-registry.com:80/nginx@sha256:asdf - name: reg-fail image: k8s.grc.io/pause - name: reg-ok2 image: also-ok-registry.com:80/nginx@sha256:asdf - name: "registry deprecated bottom" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: reg-ok1 image: ok-registry.com:80/nginx@sha256:asdf - name: reg-ok2 image: also-ok-registry.com:80/nginx@sha256:asdf - name: reg-fail image: k8s.grc.io/pause ================================================ FILE: internal/builtins/general/M-410_resource_using_invalid_restartpolicy.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-410 slug: resource-using-invalid-restartpolicy severity: Medium message: "Not allowed restartPolicy" match: resources: - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: replicasets validations: - expression: > podSpec.?restartPolicy.orValue("Always") == 'Always' ================================================ FILE: internal/builtins/general/M-410_resource_using_invalid_restartpolicy_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "restartPolicy not defined" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx - name: "restartPolicy set OnFailure" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx restartPolicy: OnFailure - name: "restartPolicy set Always" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx restartPolicy: Always - name: "restartPolicy set Never" pass: false input: | apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx restartPolicy: Never ================================================ FILE: internal/builtins/general/M-411_role_binding_referencing_anonymous_or_unauthenticated.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-411 slug: role-binding-referencing-anonymous-or-unauthenticated severity: Medium message: "Role Binding referencing anonymous user or unauthenticated group" match: resources: - group: "rbac.authorization.k8s.io" version: v1 resource: rolebindings - group: "rbac.authorization.k8s.io" version: v1 resource: clusterrolebindings validations: - expression: > !has(object.subjects) || object.subjects.all(subject, !(subject.kind == "User" && subject.name == "system:anonymous") && !(subject.kind == "Group" && subject.name == "system:unauthenticated") ) ================================================ FILE: internal/builtins/general/M-411_role_binding_referencing_anonymous_or_unauthenticated_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "anonymous user in role binding" pass: false input: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: binding-name namespace: binding-namespace roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: role-name subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: system:anonymous - kind: ServiceAccount name: zora-operator namespace: zora-system - name: "anonymous user in cluster role binding" pass: false input: | apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: binding-name roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: role-name subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: system:anonymous - kind: ServiceAccount name: zora-operator namespace: zora-system - name: "unauthenticated group in role binding" pass: false input: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: binding-name namespace: binding-namespace roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: role-name subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:unauthenticated - kind: ServiceAccount name: zora-operator namespace: zora-system - name: "unauthenticated group in cluster role binding" pass: false input: | apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: binding-name roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: role-name subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:unauthenticated - kind: ServiceAccount name: zora-operator namespace: zora-system - name: "valid role binding" pass: true input: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: binding-name roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: role-name subjects: - kind: ServiceAccount name: zora-operator namespace: zora-system - name: "valid cluster role binding" pass: true input: | apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: binding-name roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: role-name subjects: - kind: ServiceAccount name: zora-operator namespace: zora-system ================================================ FILE: internal/builtins/mitre/M-200_allowed_registries.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-200 slug: allowed-registries severity: Medium message: "Image registry not allowed" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs params: # use 'docker.io' for Docker Hub allowedRegistries: [] validations: - expression: > size(params.allowedRegistries) == 0 || allContainers.all(container, params.allowedRegistries.exists(registry, ((registry in ['docker.io', 'docker.io/library']) && !container.image.contains('/')) || container.image.startsWith(registry) ) ) message: "Container image registry not allowed" ================================================ FILE: internal/builtins/mitre/M-200_allowed_registries_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "allowedRegistries param not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "docker.io allowed" pass: true params: allowedRegistries: [docker.io, ghcr.io] input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "docker.io allowed and container using full name" pass: true params: allowedRegistries: [docker.io, ghcr.io] input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: docker.io/library/nginx:latest selector: matchLabels: app: nginx - name: "ghcr.io allowed" pass: true params: allowedRegistries: [docker.io, ghcr.io] input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: ghcr.io/nginx/nginx selector: matchLabels: app: nginx - name: "docker.io/library allowed" pass: true params: allowedRegistries: ["docker.io/library", ghcr.io] input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "not allowed" pass: false message: "Container image registry not allowed" params: allowedRegistries: [docker.io, ghcr.io] input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: gcr.io/nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/mitre/M-201_app_credentials.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-201 slug: app-credentials severity: High message: "Application credentials stored in configuration files" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs - group: "" version: v1 resource: configmaps params: sensitiveKeys: - aws_access_key_id - aws_secret_access_key - azure_batchai_storage_account - azure_batchai_storage_key - azure_batch_account - azure_batch_key - secret - key - password - pwd - token - jwt - bearer - credential sensitiveValues: - BEGIN \w+ PRIVATE KEY - PRIVATE KEY - eyJhbGciO - JWT - Bearer - key - secret validations: - expression: > object.kind != 'ConfigMap' || !has(object.data) || object.data.all(key, !params.sensitiveKeys.exists(sensitiveKey, key.lowerAscii().contains(sensitiveKey) ) && !params.sensitiveValues.exists(sensitiveValue, object.data[key].matches(sensitiveValue) ) ) message: "ConfigMap could be storing sensitive data" - expression: > allContainers.all(container, !has(container.env) || container.env.all(env, !params.sensitiveKeys.exists(sensitiveKey, env.name.lowerAscii().contains(sensitiveKey) ) && (!has(env.value) || !params.sensitiveValues.exists(sensitiveValue, env.value.matches(sensitiveValue) ) ) ) ) message: "Container could be storing sensitive data as environment variable" ================================================ FILE: internal/builtins/mitre/M-201_app_credentials_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "ConfigMap with no sensitive keys and values" pass: true input: | apiVersion: v1 kind: ConfigMap metadata: name: config data: config: value foo: bar file.yaml: content - name: "ConfigMap with no data" pass: true input: | apiVersion: v1 kind: ConfigMap metadata: name: config - name: "ConfigMap with empty data" pass: true input: | apiVersion: v1 kind: ConfigMap metadata: name: config data: {} - name: "ConfigMap with sensitive key" pass: false message: "ConfigMap could be storing sensitive data" input: | apiVersion: v1 kind: ConfigMap metadata: name: config data: config: value key: value - name: "ConfigMap with sensitive value" pass: false message: "ConfigMap could be storing sensitive data" input: | apiVersion: v1 kind: ConfigMap metadata: name: config data: foo: bar config: "PRIVATE KEY" - name: "Container with no env" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "Container with empty env" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx env: [] selector: matchLabels: app: nginx - name: "Container with no sensitive env" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx env: - name: foo value: bar selector: matchLabels: app: nginx - name: "Container with sensitive env name" pass: false message: "Container could be storing sensitive data as environment variable" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx env: - name: foo value: bar - name: key value: bar selector: matchLabels: app: nginx - name: "Container with sensitive env value" pass: false message: "Container could be storing sensitive data as environment variable" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx env: - name: foo value: "Bearer token" selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/mitre/M-202_auto_mount_service_account_token.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-202 slug: auto-mount-service-account-token severity: Low message: "Automounted service account token" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > podSpec.?automountServiceAccountToken.orValue(true) == false message: "Pod with Service Account token mounted automatically" ================================================ FILE: internal/builtins/mitre/M-202_auto_mount_service_account_token_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "automountServiceAccountToken not specified" pass: false message: "Pod with Service Account token mounted automatically" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "automountServiceAccountToken set to true" pass: false message: "Pod with Service Account token mounted automatically" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: automountServiceAccountToken: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "automountServiceAccountToken set to false" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: automountServiceAccountToken: false containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/mitre/M-203_ssh_server.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-203 slug: ssh-server severity: Low message: "SSH server running inside container" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs - group: "" version: v1 resource: services params: sshPorts: [22, 2222] validations: - expression: > object.kind != 'Service' || !has(object.spec.ports) || object.spec.ports.all(p, !(p.port in params.sshPorts) && !(p.?targetPort.orValue(0) in params.sshPorts) ) message: "Service should not be routing to SSH server" - expression: > allContainers.all(container, !has(container.ports) || container.ports.all(port, !(port.containerPort in params.sshPorts) ) ) message: "Container could be running SSH server" ================================================ FILE: internal/builtins/mitre/M-203_ssh_server_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "Service with no targetPort" pass: true input: | apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - port: 80 selector: app: nginx type: ClusterIP - name: "Service with SSH port" pass: false message: "Service should not be routing to SSH server" input: | apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - port: 22 targetPort: 80 selector: app: nginx type: ClusterIP - name: "Service with SSH targetPort" pass: false message: "Service should not be routing to SSH server" input: | apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - port: 8080 targetPort: 8080 name: http - port: 80 targetPort: 2222 name: ssh selector: app: nginx type: ClusterIP - name: "Container with no ports" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "Container with empty ports" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: [] selector: matchLabels: app: nginx - name: "Container with no SSH ports" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 selector: matchLabels: app: nginx - name: "Container with SSH port" pass: false message: "Container could be running SSH server" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 - containerPort: 22 selector: matchLabels: app: nginx - name: "Container with SSH port 2222" pass: false message: "Container could be running SSH server" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 2222 selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/nsa/M-300_read_only_root_filesystem.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: M-300 slug: read-only-root-filesystem severity: Low message: "Root filesystem write allowed" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?securityContext.?readOnlyRootFilesystem.orValue(false) == true) message: "Container is able to write to the root filesystem" ================================================ FILE: internal/builtins/nsa/M-300_read_only_root_filesystem_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: false message: "Container is able to write to the root filesystem" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.readOnlyRootFilesystem not specified" pass: false message: "Container is able to write to the root filesystem" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.readOnlyRootFilesystem set to false" pass: false message: "Container is able to write to the root filesystem" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: readOnlyRootFilesystem: false selector: matchLabels: app: nginx - name: "securityContext.readOnlyRootFilesystem set to true" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: readOnlyRootFilesystem: true selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-100_host_process.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_windowsHostProcess.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_windowsHostProcess_test.go id: M-100 slug: host-process severity: High message: "Privileged access to the Windows node" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > podSpec.?securityContext.?windowsOptions.?hostProcess.orValue(false) == false message: "Pod with privileged access to the Windows node" - expression: > allContainers.all(c, c.?securityContext.?windowsOptions.?hostProcess.orValue(false) == false) message: "Container with privileged access to the Windows node" ================================================ FILE: internal/builtins/pss/baseline/M-100_host_process_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.windowsOptions not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.windowsOptions.hostProcess not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: windowsOptions: runAsUserName: user containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.windowsOptions.hostProcess set to false" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: windowsOptions: hostProcess: false containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.windowsOptions.hostProcess set to true" pass: false message: "Pod with privileged access to the Windows node" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: os: name: windows hostNetwork: true securityContext: windowsOptions: hostProcess: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "second container with securityContext.windowsOptions.hostProcess set to true" pass: false message: "Container with privileged access to the Windows node" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: os: name: windows hostNetwork: true containers: - name: proxy image: proxy securityContext: windowsOptions: hostProcess: true - name: nginx image: nginx securityContext: windowsOptions: hostProcess: true selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-101_host_namespaces.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_hostNamespaces.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_hostNamespaces_test.go id: M-101 slug: host-namespaces severity: High message: "Host namespaces" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > podSpec.?hostNetwork.orValue(false) == false && podSpec.?hostPID.orValue(false) == false && podSpec.?hostIPC.orValue(false) == false message: "Pod sharing host namespace" ================================================ FILE: internal/builtins/pss/baseline/M-101_host_namespaces_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "host options not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "hostNetwork set to true" pass: false message: "Pod sharing host namespace" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: hostNetwork: true hostIPC: false containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "hostPID set to true" pass: false message: "Pod sharing host namespace" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: hostNetwork: false hostPID: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "hostIPC set to true" pass: false message: "Pod sharing host namespace" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: hostIPC: true hostPID: false containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "all host options set to false" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: hostNetwork: false hostIPC: false hostPID: false containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "all host options set to true" pass: false message: "Pod sharing host namespace" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: hostNetwork: true hostIPC: true hostPID: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-102_privileged_containers.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_privileged.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_privileged_test.go id: M-102 slug: privileged-containers severity: High message: "Privileged container" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?securityContext.?privileged.orValue(false) == false) message: "Container running in privileged mode" ================================================ FILE: internal/builtins/pss/baseline/M-102_privileged_containers_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.privileged not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: proxy image: proxy - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.privileged set to true" pass: false message: "Container running in privileged mode" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: proxy image: proxy - name: nginx image: nginx securityContext: privileged: true selector: matchLabels: app: nginx - name: "initContainer securityContext.privileged set to true" pass: false message: "Container running in privileged mode" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: initContainers: - name: init image: busybox securityContext: privileged: true containers: - name: proxy image: proxy - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-103_capabilities_baseline.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline_test.go id: M-103 slug: capabilities-baseline severity: High message: "Insecure capabilities" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs params: allowedCapabilities: - AUDIT_WRITE - CHOWN - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - SETFCAP - SETGID - SETPCAP - SETUID - SYS_CHROOT validations: - expression: > allContainers.all(c, c.?securityContext.?capabilities.?add.orValue([]).all(cap, cap in params.allowedCapabilities)) message: "Container running with not allowed capabilities" ================================================ FILE: internal/builtins/pss/baseline/M-103_capabilities_baseline_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.capabilities not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.capabilities.add not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: drop: [NET_BIND_SERVICE] selector: matchLabels: app: nginx - name: "securityContext.capabilities.add set with allowed capabilities" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: add: [CHOWN, NET_BIND_SERVICE, SETGID, SETUID] selector: matchLabels: app: nginx - name: "securityContext.capabilities.add set only with forbidden capabilities" pass: false message: "Container running with not allowed capabilities" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: add: [NET_ADMIN, SYS_TIME] selector: matchLabels: app: nginx - name: "securityContext.capabilities.add set with one forbidden capability" pass: false message: "Container running with not allowed capabilities" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: add: [NET_BIND_SERVICE, NET_ADMIN] selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-104_host_path_volumes.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_hostPathVolumes.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_hostPathVolumes_test.go id: M-104 slug: host-path-volumes severity: High message: "HostPath volume" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > podSpec.?volumes.orValue([]).all(v, !has(v.hostPath)) message: "Pod with mounted host volume" ================================================ FILE: internal/builtins/pss/baseline/M-104_host_path_volumes_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "volumes not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "hostPath volumes not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: volumes: - name: config configMap: name: nginx-config containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "hostPath set to null" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: volumes: - name: config hostPath: null configMap: name: nginx-config containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "hostPath volume specified" pass: false message: "Pod with mounted host volume" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: volumes: - name: cont1 hostPath: path: path containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-105_host_ports.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts_test.go id: M-105 slug: host-ports severity: High message: "Not allowed hostPort" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs params: allowedHostPorts: [0] validations: - expression: > allContainers.all(c, c.?ports.orValue([]).all(p, p.?hostPort.orValue(0) in params.allowedHostPorts)) message: "Container exposing not allowed port on the host" ================================================ FILE: internal/builtins/pss/baseline/M-105_host_ports_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "ports not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "host ports not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 selector: matchLabels: app: nginx - name: "host port set to 0" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 hostPort: 0 selector: matchLabels: app: nginx - name: "host ports specified" pass: false message: "Container exposing not allowed port on the host" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 443 - containerPort: 80 hostPort: 8080 selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-106_apparmor.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_appArmorProfile.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_appArmorProfile_test.go id: M-106 slug: apparmor severity: Medium message: "Forbidden AppArmor profile" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > !has(podMeta.annotations) || !podMeta.annotations.exists(key, key.startsWith('container.apparmor.security.beta.kubernetes.io') ) || podMeta.annotations.filter(key, key.startsWith('container.apparmor.security.beta.kubernetes.io') ).all(key, podMeta.annotations[key] == 'runtime/default' || podMeta.annotations[key].startsWith('localhost/') ) message: "Container running with forbidden AppArmor profile" ================================================ FILE: internal/builtins/pss/baseline/M-106_apparmor_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "annotations not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "AppArmor annotation not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx annotations: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "AppArmor annotation set to unconfined" pass: false message: "Container running with forbidden AppArmor profile" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx annotations: container.apparmor.security.beta.kubernetes.io/nginx: unconfined spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "AppArmor annotation set to runtime/default" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx annotations: container.apparmor.security.beta.kubernetes.io/nginx: runtime/default spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "AppArmor annotation set to localhost/*" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx annotations: container.apparmor.security.beta.kubernetes.io/nginx: localhost/bar spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-107_selinux.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_seLinuxOptions.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_seLinuxOptions_test.go id: M-107 slug: selinux severity: Medium message: "Forbidden SELinux options" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs params: allowedSELinuxTypes: - container_t - container_init_t - container_kvm_t - "" validations: - expression: > podSpec.?securityContext.?seLinuxOptions.?type.orValue("") in params.allowedSELinuxTypes message: "Pod with not allowed SELinux type" - expression: > allContainers.all(c, c.?securityContext.?seLinuxOptions.?type.orValue("") in params.allowedSELinuxTypes) message: "Container with not allowed SELinux type" - expression: > podSpec.?securityContext.?seLinuxOptions.?user.orValue("") == "" message: "Pod with forbidden SELinux user" - expression: > allContainers.all(c, c.?securityContext.?seLinuxOptions.?user.orValue("") == "") message: "Container with forbidden SELinux user" - expression: > podSpec.?securityContext.?seLinuxOptions.?role.orValue("") == "" message: "Pod with forbidden SELinux role" - expression: > allContainers.all(c, c.?securityContext.?seLinuxOptions.?role.orValue("") == "") message: "Container with forbidden SELinux role" ================================================ FILE: internal/builtins/pss/baseline/M-107_selinux_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seLinuxOptions not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seLinuxOptions.type not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: level: "s0:c123,c456" containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seLinuxOptions.type set to an empty string" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: type: "" level: "s0:c123,c456" containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seLinuxOptions.type set to container_t" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: type: container_t level: "s0:c123,c456" containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seLinuxOptions.type set to spc_t" pass: false message: "Pod with not allowed SELinux type" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: type: spc_t level: "s0:c123,c456" containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "Container securityContext.seLinuxOptions.type set to spc_t" pass: false message: "Container with not allowed SELinux type" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: type: "" level: "s0:c123,c456" containers: - name: nginx image: nginx securityContext: seLinuxOptions: type: spc_t level: "s0:c123,c456" selector: matchLabels: app: nginx - name: "Container securityContext.seLinuxOptions.type set to container_init_t and Pod to spc_t" pass: false message: "Pod with not allowed SELinux type" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: type: spc_t level: "s0:c123,c456" containers: - name: nginx image: nginx securityContext: seLinuxOptions: type: container_init_t level: "s0:c123,c456" selector: matchLabels: app: nginx - name: "Container securityContext.seLinuxOptions.user set to foo" pass: false message: "Container with forbidden SELinux user" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: type: null level: "s0:c123,c456" containers: - name: nginx image: nginx securityContext: seLinuxOptions: user: foo role: null selector: matchLabels: app: nginx - name: "Pod securityContext.seLinuxOptions.role set to bar" pass: false message: "Pod with forbidden SELinux role" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seLinuxOptions: type: null role: bar user: '' level: "s0:c123,c456" containers: - name: nginx image: nginx securityContext: seLinuxOptions: user: null role: null selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-108_proc_mount.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_procMount.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_procMount_test.go id: M-108 slug: proc-mount severity: Medium message: "Forbidden proc mount type" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > allContainers.all(c, c.?securityContext.?procMount.orValue("Default") == "Default") message: "Container using forbidden proc mount type" ================================================ FILE: internal/builtins/pss/baseline/M-108_proc_mount_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.procMount not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.procMount set to Default" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: procMount: Default selector: matchLabels: app: nginx - name: "securityContext.procMount set to Default" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: procMount: Default selector: matchLabels: app: nginx - name: "securityContext.procMount set to Unmasked" pass: false message: "Container using forbidden proc mount type" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: procMount: Unmasked selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-109_seccomp_baseline.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_seccompProfile_baseline.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_seccompProfile_baseline_test.go id: M-109 slug: seccomp-baseline severity: Medium message: "Forbidden seccomp profile" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > podSpec.?securityContext.?seccompProfile.?type.orValue("") != "Unconfined" message: "Pod using forbidden seccomp profile" - expression: > allContainers.all(c, c.?securityContext.?seccompProfile.?type.orValue("") != "Unconfined") message: "Container using forbidden seccomp profile" ================================================ FILE: internal/builtins/pss/baseline/M-109_seccomp_baseline_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seccompProfile not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: true containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.seccompProfile.type set to RuntimeDefault" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seccompProfile.type set to Localhost" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: seccompProfile: type: Localhost localhostProfile: local-profile selector: matchLabels: app: nginx - name: "Container securityContext.seccompProfile.type set to Unconfined" pass: false message: "Container using forbidden seccomp profile" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: seccompProfile: type: Unconfined selector: matchLabels: app: nginx - name: "Pod securityContext.seccompProfile.type set to Unconfined" pass: false message: "Pod using forbidden seccomp profile" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seccompProfile: type: Unconfined containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/baseline/M-110_sysctls.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_sysctls.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_sysctls_test.go id: M-110 slug: sysctls severity: Medium message: "Unsafe sysctls" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs params: allowedSysctls: - kernel.shm_rmid_forced - net.ipv4.ip_local_port_range - net.ipv4.ip_unprivileged_port_start - net.ipv4.tcp_syncookies - net.ipv4.ping_group_range variables: - name: sysctls expression: podSpec.?securityContext.?sysctls.orValue([]) validations: - expression: > variables.sysctls.size() == 0 || variables.sysctls.all(s, s.name == null || s.name in params.allowedSysctls ) message: "Pod using unsafe sysctls" ================================================ FILE: internal/builtins/pss/baseline/M-110_sysctls_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.sysctls not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.sysctls set to kernel.shm_rmid_forced" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: sysctls: - name: kernel.shm_rmid_forced value: "0" containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.sysctls set to net.ipv4.ip_unprivileged_port_start" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: sysctls: - name: net.ipv4.ip_unprivileged_port_start value: "1000" containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.sysctls set to kernel.msgmax" pass: false message: "Pod using unsafe sysctls" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: sysctls: - name: kernel.msgmax value: "65536" containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/restricted/M-111_volume_types.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_restrictedVolumes.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_hostPathVolumes_test.go id: M-111 slug: volume-types severity: Low message: "Not allowed volume type" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs params: allowedVolumeTypes: - configMap - csi - downwardAPI - emptyDir - ephemeral - persistentVolumeClaim - projected - secret variables: - name: volumes expression: podSpec.?volumes.orValue([]) validations: - expression: > variables.volumes.size() == 0 || variables.volumes.all(v, v.exists(key, key in params.allowedVolumeTypes) ) message: "Not allowed volume type used" ================================================ FILE: internal/builtins/pss/restricted/M-111_volume_types_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "volumes not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "empty volume list" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: volumes: [] containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "configMap volume" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: volumes: - name: vol configMap: name: cm containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "nfs volume" pass: false message: "Not allowed volume type used" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: volumes: - name: vol nfs: path: /path server: server containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/restricted/M-112_privilege_escalation.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_allowPrivilegeEscalation.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_allowPrivilegeEscalation_test.go id: M-112 slug: privilege-escalation severity: Medium message: "Allowed privilege escalation" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs variables: - name: isWindows expression: podSpec.?os.?name.orValue("") == "windows" validations: - expression: > variables.isWindows || allContainers.all(c, c.?securityContext.?allowPrivilegeEscalation.orValue(true) == false) ================================================ FILE: internal/builtins/pss/restricted/M-112_privilege_escalation_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.allowPrivilegeEscalation not specified" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.allowPrivilegeEscalation set to false" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: allowPrivilegeEscalation: false selector: matchLabels: app: nginx - name: "securityContext.allowPrivilegeEscalation set to true" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: allowPrivilegeEscalation: true selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/restricted/M-113_run_as_non_root.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot_test.go id: M-113 slug: run-as-non-root severity: Medium message: "Container could be running as root user" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs variables: # pod-level runAsNonRoot is explicitly set to true - name: podRunAsNonRoot expression: podSpec.?securityContext.?runAsNonRoot.orValue(false) # pod-level runAsNonRoot is explicitly set to false - name: podRunAsRoot expression: podSpec.?securityContext.?runAsNonRoot.orValue(true) == false # pod-level runAsUser is explicitly set to non-zero - name: podRunAsNonZeroUser expression: podSpec.?securityContext.?runAsUser.orValue(0) != 0 # containers that explicitly set runAsNonRoot=false - name: explicitlyBadContainers expression: > allContainers.filter(c, c.?securityContext.?runAsNonRoot.orValue(null) == false ) # containers that # - didn't set runAsNonRoot # - aren't caught by a pod-level runAsNonRoot=true # - didn't set non-zero runAsUser # - aren't caught by a pod-level non-zero runAsUser - name: implicitlyBadContainers expression: > allContainers.filter(c, (!variables.podRunAsNonRoot && c.?securityContext.?runAsNonRoot.orValue(null) == null) && (!variables.podRunAsNonZeroUser && c.?securityContext.?runAsUser.orValue(0) == 0) ) validations: - expression: > !variables.podRunAsRoot && variables.explicitlyBadContainers.size() == 0 && variables.implicitlyBadContainers.size() == 0 ================================================ FILE: internal/builtins/pss/restricted/M-113_run_as_non_root_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "Pod set runAsNonRoot to false" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: false containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "Pod set runAsNonRoot to false and container to true" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: false containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "container set runAsNonRoot to false and Pod to true" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: true containers: - name: nginx image: nginx securityContext: runAsNonRoot: false selector: matchLabels: app: nginx - name: "container set runAsNonRoot to true and Pod not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "Pod set runAsNonRoot to true and container not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: true containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "Pod set runAsUser to non-zero" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: securityContext: runAsUser: 1 containers: - name: nginx image: nginx - name: "container set runAsUser to non-zero" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsUser: 1 ================================================ FILE: internal/builtins/pss/restricted/M-114_run_as_user.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser_test.go id: M-114 slug: run-as-user severity: Medium message: "Container running as root UID" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs validations: - expression: > podSpec.?securityContext.?runAsUser.orValue(-1) != 0 && allContainers.all(c, c.?securityContext.?runAsUser.orValue(-1) != 0) message: "Container running as root UID" ================================================ FILE: internal/builtins/pss/restricted/M-114_run_as_user_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "Pod explicitly set runAsUser to 0 and Container to 1000" pass: false message: "Container running as root UID" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsUser: 0 containers: - name: nginx image: nginx securityContext: runAsUser: 1000 selector: matchLabels: app: nginx - name: "Container explicitly set runAsUser to 0 and Pod to 1000" pass: false message: "Container running as root UID" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsUser: 1000 containers: - name: nginx image: nginx securityContext: runAsUser: 0 selector: matchLabels: app: nginx - name: "Container set runAsUser to 1000 and Pod not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsUser: 1000 selector: matchLabels: app: nginx - name: "Pod set runAsUser to 1000 and Container not specified" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsUser: 1000 containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/restricted/M-115_seccomp_restricted.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_seccompProfile_restricted.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_seccompProfile_restricted_test.go id: M-115 slug: seccomp-restricted severity: Low message: "Not allowed seccomp profile" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs variables: # pod-level seccompProfile is explicitly different to Unconfined - name: podSeccompSet expression: podSpec.?securityContext.?seccompProfile.?type.orValue("Unconfined") != "Unconfined" # pod-level seccompProfile is explicitly Unconfined - name: podSeccompUnconfined expression: podSpec.?securityContext.?seccompProfile.?type.orValue("") == "Unconfined" # containers that explicitly set seccompProfile.type to Unconfined - name: explicitlyBadContainers expression: allContainers.filter(c, c.?securityContext.?seccompProfile.?type.orValue("") == "Unconfined") # containers that didn't set seccompProfile and aren't caught by a pod-level seccompProfile - name: implicitlyBadContainers expression: > allContainers.filter(c, !variables.podSeccompSet && c.?securityContext.?seccompProfile.orValue(null) == null ) - name: isWindows expression: podSpec.?os.?name.orValue("") == "windows" validations: - expression: > variables.isWindows || ( !variables.podSeccompUnconfined && variables.explicitlyBadContainers.size() == 0 && variables.implicitlyBadContainers.size() == 0 ) ================================================ FILE: internal/builtins/pss/restricted/M-115_seccomp_restricted_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seccompProfile not specified" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: runAsNonRoot: true containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.seccompProfile.type set to RuntimeDefault" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.seccompProfile.type set to Localhost" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: seccompProfile: type: Localhost localhostProfile: local-profile selector: matchLabels: app: nginx - name: "Container securityContext.seccompProfile.type set to Unconfined" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: nginx image: nginx securityContext: seccompProfile: type: Unconfined selector: matchLabels: app: nginx - name: "Pod securityContext.seccompProfile.type set to Unconfined" pass: false input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: securityContext: seccompProfile: type: Unconfined containers: - name: nginx image: nginx securityContext: seccompProfile: type: Localhost localhostProfile: local-profile selector: matchLabels: app: nginx ================================================ FILE: internal/builtins/pss/restricted/M-116_capabilities_restricted.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_restricted.go # https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_restricted_test.go id: M-116 slug: capabilities-restricted severity: Low message: "Not allowed added/dropped capabilities" match: resources: - group: "" version: v1 resource: pods - group: apps version: v1 resource: deployments - group: apps version: v1 resource: daemonsets - group: apps version: v1 resource: statefulsets - group: apps version: v1 resource: replicasets - group: batch version: v1 resource: cronjobs - group: batch version: v1 resource: jobs params: allowedCapabilities: - NET_BIND_SERVICE variables: - name: isWindows expression: podSpec.?os.?name.orValue("") == "windows" validations: - expression: > variables.isWindows || allContainers.all(c, c.?securityContext.?capabilities.?drop.orValue([]).exists(ca, ca == 'ALL')) message: "Containers must drop ALL capabilities" - expression: > variables.isWindows || allContainers.all(c, c.?securityContext.?capabilities.?add.orValue([]).all(ca, ca in params.allowedCapabilities)) message: "Container running with not allowed capabilities" ================================================ FILE: internal/builtins/pss/restricted/M-116_capabilities_restricted_test.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: "securityContext not specified" pass: false message: "Containers must drop ALL capabilities" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: "securityContext.capabilities not specified" pass: false message: "Containers must drop ALL capabilities" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: runAsNonRoot: true selector: matchLabels: app: nginx - name: "securityContext.capabilities.drop not includes ALL" pass: false message: "Containers must drop ALL capabilities" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: drop: [KILL] selector: matchLabels: app: nginx - name: "securityContext.capabilities.drop includes ALL" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: drop: [KILL, ALL, CHOWN] selector: matchLabels: app: nginx - name: "securityContext.capabilities.add includes not allowed capabilities" pass: false message: "Container running with not allowed capabilities" input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: drop: [ALL] add: [CHOWN, NET_BIND_SERVICE, SETGID, SETUID] selector: matchLabels: app: nginx - name: "securityContext.capabilities.add only NET_BIND_SERVICE" pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx securityContext: capabilities: drop: [ALL] add: [NET_BIND_SERVICE] selector: matchLabels: app: nginx - name: "windows" pass: true input: | apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: os: name: windows containers: - name: nginx image: nginx securityContext: {} ================================================ FILE: main.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import "github.com/undistro/marvin/cmd" func main() { cmd.Execute() } ================================================ FILE: pkg/cmd/scan.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "errors" "fmt" "os" "strings" "github.com/fatih/color" "github.com/go-logr/logr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/version" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/dynamic" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/utils/pointer" "github.com/spf13/pflag" "github.com/undistro/marvin/pkg/loader" "github.com/undistro/marvin/pkg/printers" "github.com/undistro/marvin/pkg/types" "github.com/undistro/marvin/pkg/validator" ) type ScanOptions struct { *genericclioptions.ConfigFlags genericclioptions.IOStreams ChecksPath *string DisableBuiltIn *bool OutputFormat *string NoFail *bool SkipAnnotation *string DisableAnnotationSkip *bool CostLimit *uint64 ctx context.Context log logr.Logger printer printers.Printer client *dynamic.DynamicClient kubeVersion *version.Info apiResources []*metav1.APIResourceList resources map[string][]unstructured.Unstructured gvrs map[string]string } // NewScanOptions returns a ScanOptions with the default values func NewScanOptions() *ScanOptions { return &ScanOptions{ ConfigFlags: genericclioptions.NewConfigFlags(false), IOStreams: genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}, ChecksPath: pointer.String(""), DisableBuiltIn: pointer.Bool(false), OutputFormat: pointer.String("table"), NoFail: pointer.Bool(false), DisableAnnotationSkip: pointer.Bool(false), SkipAnnotation: pointer.String("marvin.undistro.io/skip"), CostLimit: pointer.Uint64(1000000), } } // AddFlags binds scan configuration flags to a given flagset func (o *ScanOptions) AddFlags(flags *pflag.FlagSet) { o.ConfigFlags.AddFlags(flags) if o.ChecksPath != nil { flags.StringVarP(o.ChecksPath, "checks", "f", *o.ChecksPath, "Path to the check files directory") } if o.DisableBuiltIn != nil { flags.BoolVar(o.DisableBuiltIn, "disable-builtin", *o.DisableBuiltIn, "Disable builtin checks") } if o.OutputFormat != nil { flags.StringVarP(o.OutputFormat, "output", "o", *o.OutputFormat, `Output format. One of: ("table", "json", "yaml" or "markdown")`) } if o.NoFail != nil { flags.BoolVar(o.NoFail, "no-fail", *o.NoFail, "Return an exit code of zero even if there are errors in the report") } if o.SkipAnnotation != nil { flags.StringVar(o.SkipAnnotation, "skip-annotation", *o.SkipAnnotation, "Annotation name for skipping checks") } if o.DisableAnnotationSkip != nil { flags.BoolVar(o.DisableAnnotationSkip, "disable-annotation-skip", *o.DisableAnnotationSkip, "Disable resource skipping by annotation") } if o.CostLimit != nil { flags.Uint64Var(o.CostLimit, "cost-limit", *o.CostLimit, "CEL cost limit. Set 0 to disable it.") } } // Init initializes the kubernetes clients, get server version and API resources func (o *ScanOptions) Init(ctx context.Context) error { if err := o.Validate(); err != nil { return err } o.ctx = ctx o.log = logr.FromContextOrDiscard(o.ctx) var printer printers.Printer switch *o.OutputFormat { case "json": printer = &printers.JSONPrinter{} case "yaml": printer = &printers.YAMLPrinter{} case "table": printer = &printers.TablePrinter{} case "markdown": color.NoColor = true printer = &printers.MarkdownPrinter{} default: return fmt.Errorf("invalid output format '%s'", *o.OutputFormat) } dynamicClient, err := o.ToDynamicClient() if err != nil { return fmt.Errorf("dynamic client error: %s", err.Error()) } discoveryClient, err := o.ToDiscoveryClient() if err != nil { return fmt.Errorf("kubernetes client error: %s", err.Error()) } kubeVersion, err := discoveryClient.ServerVersion() if err != nil { return fmt.Errorf("server version error: %s", err.Error()) } _, apiResources, err := discoveryClient.ServerGroupsAndResources() if err != nil { return fmt.Errorf("server groups error: %s", err.Error()) } o.client = dynamicClient o.kubeVersion = kubeVersion o.apiResources = apiResources o.printer = printer o.resources = make(map[string][]unstructured.Unstructured) o.gvrs = make(map[string]string) return nil } // Validate ensures that all required arguments and flag values are provided func (o *ScanOptions) Validate() error { if *o.DisableBuiltIn == true && *o.ChecksPath == "" { return errors.New(`please set '--checks/-f' or keep 'disable-builtin' 'false'`) } return nil } // ToDynamicClient returns a DynamicClient using a computed RESTConfig. func (o *ScanOptions) ToDynamicClient() (*dynamic.DynamicClient, error) { config, err := o.ToRESTConfig() if err != nil { return nil, err } return dynamic.NewForConfig(config) } // Run executes the scan command func (o *ScanOptions) Run() (bool, error) { allChecks, err := o.getChecks() if err != nil { return false, err } report := types.NewReport(o.kubeVersion) for _, check := range allChecks { cr := o.runCheck(check) report.Add(cr) } report.GVRs = o.gvrs hasError := report.HasError() if hasError { o.log.Info("scan finished with errors") } return hasError, o.printer.PrintObj(*report, o.Out) } // getChecks returns a list of checks.Check based on the flags, including built-in checks or/and from a path. func (o *ScanOptions) getChecks() ([]types.Check, error) { o.log.V(3).Info("loading checks", "builtin", !*o.DisableBuiltIn, "custom", *o.ChecksPath != "") var allChecks []types.Check if !*o.DisableBuiltIn { allChecks = loader.Builtins o.log.V(3).Info("builtin checks loaded", "total", len(loader.Builtins)) } if *o.ChecksPath != "" { localChecks, err := loader.LoadChecks(*o.ChecksPath) if err != nil { o.log.Error(err, "failed to load checks", "path", *o.ChecksPath) return nil, fmt.Errorf("load checks error: %s", err.Error()) } o.log.V(2).Info("custom checks loaded", "total", len(localChecks)) allChecks = append(allChecks, localChecks...) } return allChecks, nil } // runCheck performs a check func (o *ScanOptions) runCheck(check types.Check) *types.CheckResult { log := o.log.WithValues("check", check.ID) cr := types.NewCheckResult(check) defer cr.UpdateStatus() v, err := validator.Compile(check, o.apiResources, o.kubeVersion, *o.CostLimit) if err != nil { log.Error(err, "failed to compile check "+check.ID) cr.AddError(fmt.Errorf("%s compile error: %s", check.Path, err.Error())) return cr } log.V(3).Info("check compiled successfully") resources, errs := o.loadResources(check) cr.AddErrors(errs...) for gvr, objs := range resources { for _, obj := range objs { log := log.WithValues("obj", fmt.Sprintf("%s/%s", types.GVK(obj), types.NamespacedName(obj))) o.addGVR(obj, gvr) if o.isSkipped(check.ID, obj.GetAnnotations()) { log.V(4).Info("skipped") cr.AddSkipped(obj) continue } passed, _, err := v.Validate(obj, check.Params) if err != nil { log.Error(err, "failed to validate check "+check.ID) cr.AddError(fmt.Errorf("%s validate error: %s", check.Path, err.Error())) continue } if passed { log.V(4).Info("passed") cr.AddPassed(obj) } else { log.V(4).Info("failed") cr.AddFailed(obj) } } } return cr } // loadResources returns a map of resource slice by GVR to be validated by the given check func (o *ScanOptions) loadResources(check types.Check) (map[string][]unstructured.Unstructured, []error) { resources := map[string][]unstructured.Unstructured{} var errs []error for _, r := range check.Match.Resources { gvr := r.ToGVR() gvrs := fmt.Sprintf("%s/%s", gvr.GroupVersion().String(), gvr.Resource) log := o.log.WithValues("check", check.ID) objs, cached := o.resources[gvrs] if cached { log.V(3).Info(gvrs+" resources cached", "total", len(objs)) resources[gvrs] = objs } else { log.V(3).Info(fmt.Sprintf("listing %s from kubernetes", gvrs)) ul, err := o.client.Resource(gvr).Namespace(*o.Namespace).List(o.ctx, metav1.ListOptions{}) if err != nil { log.Error(err, "failed to list "+gvrs) errs = append(errs, fmt.Errorf("list %s error: %s", gvr.Resource, err.Error())) continue } log.V(1).Info(gvrs+" loaded from kubernetes", "total", len(ul.Items)) o.resources[gvrs] = ul.Items resources[gvrs] = ul.Items } } return resources, errs } // isSkipped returns true if the checkID is annotated to be skipped func (o *ScanOptions) isSkipped(checkID string, annotations map[string]string) bool { if annotations == nil { return false } if *o.DisableAnnotationSkip { return false } v, ok := annotations[*o.SkipAnnotation] if !ok { return false } ids := strings.Split(v, ",") for _, s := range ids { if strings.TrimSpace(s) == checkID { return true } } return false } // addGVR updates the map of GVR by GVK func (o *ScanOptions) addGVR(obj unstructured.Unstructured, gvr string) { gvk := types.GVK(obj) if _, ok := o.gvrs[gvk]; !ok { o.gvrs[gvk] = gvr } } ================================================ FILE: pkg/loader/builtin.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package loader import ( "io/fs" "log" "github.com/undistro/marvin/internal/builtins" "github.com/undistro/marvin/pkg/types" ) // Builtins represents the builtins checks var Builtins []types.Check func init() { c, _, walkFn := walkDir(builtins.EmbedChecksFS.ReadFile, true) err := fs.WalkDir(builtins.EmbedChecksFS, ".", walkFn) if err != nil { log.Fatal(err) } Builtins = c.toList() } ================================================ FILE: pkg/loader/builtin_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package loader import ( "testing" "github.com/stretchr/testify/assert" ) func TestBuiltins(t *testing.T) { assert.NotNil(t, Builtins) assert.Greater(t, len(Builtins), 0) assert.Equal(t, 35, len(Builtins)) } ================================================ FILE: pkg/loader/loader.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package loader import ( "encoding/json" "fmt" "io/fs" "os" "path/filepath" "strings" "sigs.k8s.io/yaml" "github.com/undistro/marvin/pkg/types" ) type ( ChecksMap map[string]types.Check TestsMap map[string][]types.Test readFileFunc func(string) ([]byte, error) ) // toList returns a slice of Check func (cm ChecksMap) toList() []types.Check { if cm == nil { return nil } list := make([]types.Check, 0, len(cm)) for _, c := range cm { list = append(list, c) } return list } // supportedExt supported file extensions for checks and tests var supportedExt = map[string]bool{ ".yaml": true, ".yml": true, ".json": true, } // LoadChecks loads all checks from given path recursively func LoadChecks(root string) ([]types.Check, error) { c, _, err := load(root) return c.toList(), err } // LoadChecksAndTests loads all checks and their tests from given path recursively func LoadChecksAndTests(root string) (ChecksMap, TestsMap, error) { return load(root) } func load(root string) (ChecksMap, TestsMap, error) { check, tests, walkFn := walkDir(os.ReadFile, false) err := filepath.WalkDir(root, walkFn) if err != nil { return nil, nil, err } return check, tests, nil } func walkDir(readFileFn readFileFunc, builtin bool) (ChecksMap, TestsMap, fs.WalkDirFunc) { tests := make(TestsMap) check := make(ChecksMap) return check, tests, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err } ext := filepath.Ext(path) if !supportedExt[ext] { return nil // unsupported file } bs, err := readFileFn(path) if err != nil { return err } testSuffix := "_test" + ext isTest := strings.HasSuffix(path, testSuffix) if isTest { t, err := parseTests(ext, bs) if err != nil { return err } k := strings.TrimSuffix(path, testSuffix) tests[k] = t return nil } c, err := parseCheck(ext, bs) if err != nil { return err } c.Builtin = builtin c.Path = path k := strings.TrimSuffix(path, ext) if builtin { k = "builtin:" + k } check[k] = c return nil } } func parseCheck(ext string, bs []byte) (types.Check, error) { obj := types.Check{} return parse(ext, bs, obj) } func parseTests(ext string, bs []byte) ([]types.Test, error) { var obj []types.Test return parse(ext, bs, obj) } func parse[T any](ext string, bs []byte, obj T) (T, error) { var err error switch ext { case ".yaml", ".yml": err = yaml.Unmarshal(bs, &obj) case ".json": err = json.Unmarshal(bs, &obj) default: return obj, fmt.Errorf("unsupported file extension: %s", ext) } return obj, err } ================================================ FILE: pkg/loader/loader_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package loader import ( "testing" "github.com/stretchr/testify/assert" ) func TestLoadChecks(t *testing.T) { emptyDir := t.TempDir() assert.NotEmpty(t, emptyDir) tests := []struct { name string path string wantLen int wantErr bool }{ { name: "recursive", path: "testdata/checks/", wantLen: 2, wantErr: false, }, { name: "depth 1", path: "testdata/checks/workloads/", wantLen: 1, wantErr: false, }, { name: "valid file", path: "testdata/checks/workloads/replicas.yaml", wantLen: 1, wantErr: false, }, { name: "invalid file", path: "testdata/checks/workloads/unsupported.txt", wantLen: 0, wantErr: false, }, { name: "not found", path: "testdata/checks/notfound", wantErr: true, }, { name: "empty", path: emptyDir, wantErr: false, wantLen: 0, }, { name: "invalid YAML", path: "testdata/invalid/", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := LoadChecks(tt.path) if (err != nil) != tt.wantErr { t.Errorf("LoadChecks() error = %v, wantErr %v", err, tt.wantErr) return } if len(got) != tt.wantLen { t.Errorf("LoadChecks() got = %v items, want %v", len(got), tt.wantLen) } }) } } func TestLoadChecksAndTests(t *testing.T) { emptyDir := t.TempDir() assert.NotEmpty(t, emptyDir) tests := []struct { name string path string wantChecksLen int wantTestsLen int wantErr bool }{ { name: "recursive", path: "testdata/checks/", wantChecksLen: 2, wantTestsLen: 1, wantErr: false, }, { name: "depth 1", path: "testdata/checks/workloads/", wantChecksLen: 1, wantTestsLen: 1, wantErr: false, }, { name: "valid file", path: "testdata/checks/workloads/replicas.yaml", wantChecksLen: 1, wantErr: false, }, { name: "invalid file", path: "testdata/checks/workloads/unsupported.txt", wantChecksLen: 0, wantErr: false, }, { name: "not found", path: "testdata/checks/notfound", wantErr: true, }, { name: "empty", path: emptyDir, wantErr: false, wantChecksLen: 0, }, { name: "invalid YAML", path: "testdata/invalid/", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotChecks, gotTests, err := LoadChecksAndTests(tt.path) if (err != nil) != tt.wantErr { t.Errorf("LoadChecksAndTests() error = %v, wantErr %v", err, tt.wantErr) return } if len(gotChecks) != tt.wantChecksLen { t.Errorf("LoadChecksAndTests() got = %v items, want %v", len(gotChecks), tt.wantChecksLen) } if len(gotTests) != tt.wantTestsLen { t.Errorf("LoadChecksAndTests() got = %v items, want %v", len(gotTests), tt.wantTestsLen) } }) } } ================================================ FILE: pkg/loader/testdata/checks/svc_lb.json ================================================ { "id": "TEST-002", "message": "Type Loadbalancer detected. Could be expensive", "severity": "Low", "match": { "resources": [ { "group": "", "version": "v1", "resource": "services" } ] }, "validations": [ { "expression": "object.spec.type != 'LoadBalancer'", "message": "Type Loadbalancer detected. Could be expensive" } ] } ================================================ FILE: pkg/loader/testdata/checks/workloads/replicas.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. id: TEST-001 severity: Low message: "minimum quantity of replicas (2) not reached" match: resources: - group: apps version: v1 resource: deployments validations: - expression: "object.spec.replicas >= 2" message: "minimum quantity of replicas (2) not reached" ================================================ FILE: pkg/loader/testdata/checks/workloads/replicas_test.yaml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - name: 'replicas set to 1' pass: false message: 'minimum quantity of replicas (2) not reached' input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 1 template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: 'not specified' pass: false message: 'minimum quantity of replicas (2) not reached' input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx - name: 'replicas set to 2' pass: true input: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 2 template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx ================================================ FILE: pkg/loader/testdata/checks/workloads/unsupported.txt ================================================ unsupported file type ================================================ FILE: pkg/loader/testdata/invalid/invalid.yml ================================================ # Copyright 2023 Undistro Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. invalid YAML ================================================ FILE: pkg/printers/interface.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package printers import ( "io" "github.com/undistro/marvin/pkg/types" ) // Printer is an interface that defines a method for printing a Report to an io.Writer type Printer interface { PrintObj(types.Report, io.Writer) error } ================================================ FILE: pkg/printers/json.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package printers import ( "encoding/json" "io" "github.com/undistro/marvin/pkg/types" ) // JSONPrinter implements a Printer that prints the report in JSON format type JSONPrinter struct{} func (*JSONPrinter) PrintObj(report types.Report, w io.Writer) error { data, err := json.Marshal(report) if err != nil { return err } data = append(data, '\n') _, err = w.Write(data) return err } ================================================ FILE: pkg/printers/md.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package printers import ( "io" "github.com/olekukonko/tablewriter" "github.com/undistro/marvin/pkg/types" ) // MarkdownPrinter implements a Printer that prints the report in Markdown format type MarkdownPrinter struct{} func (*MarkdownPrinter) PrintObj(report types.Report, w io.Writer) error { t := tablewriter.NewWriter(w) t.SetAutoWrapText(false) t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) t.SetAlignment(tablewriter.ALIGN_LEFT) t.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) t.SetCenterSeparator("|") renderTable(report, t) return nil } ================================================ FILE: pkg/printers/table.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package printers import ( "io" "sort" "strconv" "strings" "github.com/fatih/color" "github.com/olekukonko/tablewriter" "github.com/undistro/marvin/pkg/types" ) var ( red = color.New(color.FgRed).SprintfFunc() redBold = color.New(color.FgRed, color.Bold).SprintfFunc() yellow = color.New(color.FgYellow).SprintfFunc() blue = color.New(color.FgBlue).SprintfFunc() green = color.New(color.FgGreen).SprintfFunc() ) // TablePrinter implements a Printer that prints the report in table format type TablePrinter struct { } func (r *TablePrinter) PrintObj(report types.Report, w io.Writer) error { t := tablewriter.NewWriter(w) t.SetAutoWrapText(false) t.SetAutoFormatHeaders(true) t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) t.SetAlignment(tablewriter.ALIGN_LEFT) t.SetCenterSeparator("") t.SetColumnSeparator("") t.SetRowSeparator("") t.SetHeaderLine(false) t.SetBorder(false) t.SetTablePadding(" ") t.SetNoWhiteSpace(true) renderTable(report, t) return nil } func renderTable(report types.Report, t *tablewriter.Table) { t.SetHeader([]string{"SEVERITY", "ID", "CHECK", "STATUS", "FAILED", "PASSED", "SKIPPED"}) sort.Slice(report.Checks, func(i, j int) bool { if report.Checks[i].Severity != report.Checks[j].Severity { return report.Checks[i].Severity > report.Checks[j].Severity } if report.Checks[i].TotalFailed != report.Checks[j].TotalFailed { return report.Checks[i].TotalFailed > report.Checks[j].TotalFailed } if report.Checks[i].TotalPassed != report.Checks[j].TotalPassed { return report.Checks[i].TotalPassed > report.Checks[j].TotalPassed } return strings.Compare(report.Checks[i].ID, report.Checks[j].ID) < 0 }) for _, c := range report.Checks { t.Append([]string{ colorSeverity(c.Severity), c.ID, c.Message, colorStatus(c.Status), strconv.Itoa(c.TotalFailed), strconv.Itoa(c.TotalPassed), strconv.Itoa(c.TotalSkipped), }) } t.Render() } func colorSeverity(s types.Severity) string { switch s { case types.SeverityLow: return blue("%s", s) case types.SeverityMedium: return yellow("%s", s) case types.SeverityHigh: return red("%s", s) case types.SeverityCritical: return redBold("%s", s) default: return s.String() } } func colorStatus(s types.CheckStatus) string { switch s { case types.StatusPassed: return green("%s", s) case types.StatusSkipped: return blue("%s", s) case types.StatusFailed: return red("%s", s) case types.StatusError: return redBold("%s", strings.ToUpper(s.String())) default: return s.String() } } ================================================ FILE: pkg/printers/yaml.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package printers import ( "fmt" "io" "sigs.k8s.io/yaml" "github.com/undistro/marvin/pkg/types" ) // YAMLPrinter implements a Printer that prints the report in YAML format type YAMLPrinter struct{} func (*YAMLPrinter) PrintObj(report types.Report, w io.Writer) error { data, err := yaml.Marshal(report) if err != nil { return err } _, err = fmt.Fprint(w, string(data)) return err } ================================================ FILE: pkg/types/check.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" ) type Check struct { ID string `json:"id"` Match Match `json:"match"` Validations []Validation `json:"validations"` Variables []Variable `json:"variables"` Params map[string]any `json:"params"` Severity Severity `json:"severity"` Message string `json:"message"` Labels map[string]string `json:"labels,omitempty"` Builtin bool `json:"builtin"` Path string `json:"path,omitempty"` } type Match struct { Resources []ResourceRule `json:"resources"` } type ResourceRule struct { Group string `json:"group,omitempty"` Version string `json:"version"` Resource string `json:"resource"` } func (r *ResourceRule) ToGVR() schema.GroupVersionResource { return schema.GroupVersionResource{Group: r.Group, Version: r.Version, Resource: r.Resource} } type Validation struct { Expression string `json:"expression"` Message string `json:"message,omitempty"` } type Variable struct { Name string `json:"name"` Expression string `json:"expression"` } type Test struct { Name string `json:"name"` Input string `json:"input"` Params any `json:"params"` APIVersions []string `json:"apiVersions"` KubeVersion *version.Info `json:"kubeVersion"` Pass bool `json:"pass"` Message string `json:"message"` } ================================================ FILE: pkg/types/check_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "testing" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestResourceRule_ToGVR(t *testing.T) { type fields struct { Group string Version string Resource string } tests := []struct { name string fields fields want schema.GroupVersionResource }{ { name: "services", fields: fields{ Group: "", Version: "v1", Resource: "services", }, want: corev1.SchemeGroupVersion.WithResource("services"), }, { name: "deployments", fields: fields{ Group: "apps", Version: "v1", Resource: "deployments", }, want: appsv1.SchemeGroupVersion.WithResource("deployments"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &ResourceRule{ Group: tt.fields.Group, Version: tt.fields.Version, Resource: tt.fields.Resource, } assert.Equalf(t, tt.want, r.ToGVR(), "ToGVR()") }) } } ================================================ FILE: pkg/types/report.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "fmt" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/version" ) type Report struct { KubeVersion *version.Info `json:"kubeVersion"` Checks []*CheckResult `json:"checks"` GVRs map[string]string `json:"gvrs,omitempty"` } func NewReport(kubeVersion *version.Info) *Report { return &Report{KubeVersion: kubeVersion} } func (r *Report) Add(cr *CheckResult) { r.Checks = append(r.Checks, cr) } func (r *Report) HasError() bool { for _, check := range r.Checks { if len(check.Errors) > 0 { return true } } return false } type CheckResult struct { ID string `json:"id"` Message string `json:"message"` Severity Severity `json:"severity"` Builtin bool `json:"builtin"` Path string `json:"path"` Labels map[string]string `json:"labels,omitempty"` Status CheckStatus `json:"status"` Failed map[string][]string `json:"failed"` Passed map[string][]string `json:"passed"` Skipped map[string][]string `json:"skipped"` Errors []string `json:"errors"` TotalFailed int `json:"totalFailed"` TotalPassed int `json:"totalPassed"` TotalSkipped int `json:"totalSkipped"` } func NewCheckResult(check Check) *CheckResult { return &CheckResult{ ID: check.ID, Message: check.Message, Severity: check.Severity, Builtin: check.Builtin, Path: check.Path, Labels: check.Labels, Failed: map[string][]string{}, Passed: map[string][]string{}, Skipped: map[string][]string{}, Errors: []string{}, } } func (r *CheckResult) AddFailed(obj unstructured.Unstructured) { addResource(obj, r.Failed) r.TotalFailed++ } func (r *CheckResult) AddPassed(obj unstructured.Unstructured) { addResource(obj, r.Passed) r.TotalPassed++ } func (r *CheckResult) AddSkipped(obj unstructured.Unstructured) { addResource(obj, r.Skipped) r.TotalSkipped++ } func (r *CheckResult) AddError(err error) { r.Errors = append(r.Errors, err.Error()) } func (r *CheckResult) AddErrors(errs ...error) { for _, err := range errs { r.AddError(err) } } func (r *CheckResult) UpdateStatus() { if len(r.Errors) > 0 { r.Status = StatusError return } if len(r.Failed) > 0 { r.Status = StatusFailed return } if len(r.Passed) == 0 && len(r.Skipped) > 0 { r.Status = StatusSkipped return } r.Status = StatusPassed return } // GVK returns the GroupVersionKind string of the given resource func GVK(obj unstructured.Unstructured) string { gvk := obj.GroupVersionKind() return fmt.Sprintf("%s/%s", gvk.GroupVersion().String(), gvk.Kind) } // NamespacedName returns the namespaced name string of the given resource func NamespacedName(obj unstructured.Unstructured) string { if len(obj.GetNamespace()) > 0 { return fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName()) } return obj.GetName() } func addResource(obj unstructured.Unstructured, m map[string][]string) { k := GVK(obj) v := NamespacedName(obj) if _, ok := m[k]; ok { m[k] = append(m[k], v) } else { m[k] = []string{v} } } ================================================ FILE: pkg/types/report_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "errors" "testing" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/version" ) var ( kubeVersion = &version.Info{ Major: "1", Minor: "25", GitVersion: "v1.25.3", } check = Check{ ID: "foo", Severity: SeverityHigh, Message: "bar", Builtin: true, Path: "path.yml", } ) func TestReport(t *testing.T) { rep := NewReport(kubeVersion) assert.NotNil(t, rep) assert.NotNil(t, rep.KubeVersion) assert.Equal(t, "25", rep.KubeVersion.Minor) assert.Len(t, rep.Checks, 0) cr := NewCheckResult(check) assert.NotNil(t, cr) assert.Equal(t, "foo", cr.ID) assert.Equal(t, "bar", cr.Message) assert.Equal(t, SeverityHigh, cr.Severity) assert.True(t, cr.Builtin) assert.Equal(t, "path.yml", cr.Path) assert.Len(t, cr.Failed, 0) assert.Len(t, cr.Passed, 0) assert.Len(t, cr.Skipped, 0) assert.Len(t, cr.Errors, 0) cr.AddSkipped(obj("apps/v1", "Deployment", "ns", "skipped-deploy-1")) cr.AddSkipped(obj("apps/v1", "Deployment", "ns", "skipped-deploy-2")) cr.AddSkipped(obj("v1", "Pod", "", "skipped-pod")) assert.Len(t, cr.Skipped, 2) assert.Len(t, cr.Skipped["apps/v1/Deployment"], 2) assert.Len(t, cr.Skipped["v1/Pod"], 1) assert.Len(t, cr.Failed, 0) assert.Len(t, cr.Passed, 0) assert.Len(t, cr.Errors, 0) cr.UpdateStatus() assert.Equal(t, StatusSkipped, cr.Status) cr.AddPassed(obj("v1", "Pod", "ns", "passed-1")) cr.AddPassed(obj("v1", "Pod", "", "passed-2")) assert.Len(t, cr.Skipped, 2) assert.Len(t, cr.Passed, 1) assert.Len(t, cr.Passed["v1/Pod"], 2) assert.Equal(t, cr.Passed["v1/Pod"][0], "ns/passed-1") assert.Equal(t, cr.Passed["v1/Pod"][1], "passed-2") assert.Len(t, cr.Failed, 0) assert.Len(t, cr.Errors, 0) cr.UpdateStatus() assert.Equal(t, StatusPassed, cr.Status) cr.AddFailed(obj("batch/v1", "CronJob", "ns", "failed")) assert.Len(t, cr.Skipped, 2) assert.Len(t, cr.Passed, 1) assert.Len(t, cr.Failed, 1) assert.Len(t, cr.Failed["batch/v1/CronJob"], 1) assert.Len(t, cr.Errors, 0) cr.UpdateStatus() assert.Equal(t, StatusFailed, cr.Status) cr.AddError(errors.New("list deployments error")) assert.Len(t, cr.Skipped, 2) assert.Len(t, cr.Passed, 1) assert.Len(t, cr.Failed, 1) assert.Len(t, cr.Errors, 1) cr.UpdateStatus() assert.Equal(t, StatusError, cr.Status) rep.Add(cr) assert.Len(t, rep.Checks, 1) } func obj(apiVersion, kind, ns, name string) unstructured.Unstructured { return unstructured.Unstructured{Object: map[string]any{ "apiVersion": apiVersion, "kind": kind, "metadata": map[string]any{"namespace": ns, "name": name}, }} } ================================================ FILE: pkg/types/severity.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "fmt" "strings" ) type Severity int const ( SeverityUnknown Severity = iota SeverityLow SeverityMedium SeverityHigh SeverityCritical ) func (s Severity) String() string { switch s { case SeverityLow: return "Low" case SeverityMedium: return "Medium" case SeverityHigh: return "High" case SeverityCritical: return "Critical" default: return "" } } func ParseSeverity(s string) Severity { switch s { case "Low": return SeverityLow case "Medium": return SeverityMedium case "High": return SeverityHigh case "Critical": return SeverityCritical default: return SeverityUnknown } } func (s *Severity) UnmarshalJSON(b []byte) error { *s = ParseSeverity(strings.Trim(string(b), "\"")) return nil } func (s Severity) MarshalJSON() ([]byte, error) { v := fmt.Sprintf(`"%s"`, s.String()) return []byte(v), nil } ================================================ FILE: pkg/types/severity_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" ) type severityHolder struct { Severity Severity `json:"severity"` } func TestSeverityMarshalJSON(t *testing.T) { tests := []struct { input Severity want string }{ { input: SeverityLow, want: `{"severity":"Low"}`, }, { input: SeverityMedium, want: `{"severity":"Medium"}`, }, { input: SeverityHigh, want: `{"severity":"High"}`, }, { input: SeverityCritical, want: `{"severity":"Critical"}`, }, { input: SeverityCritical, want: `{"severity":"Critical"}`, }, { input: SeverityUnknown, want: `{"severity":""}`, }, } for _, tt := range tests { t.Run(tt.input.String(), func(t *testing.T) { input := severityHolder{tt.input} got, err := json.Marshal(&input) assert.NoError(t, err) assert.Equal(t, tt.want, string(got)) }) } } func TestSeverityUnmarshalJSON(t *testing.T) { tests := []struct { input string want Severity }{ { input: `{"severity": "Low"}`, want: SeverityLow, }, { input: `{"severity": "Medium"}`, want: SeverityMedium, }, { input: `{"severity": "High"}`, want: SeverityHigh, }, { input: `{"severity": "Critical"}`, want: SeverityCritical, }, { input: `{"severity": "Critical"}`, want: SeverityCritical, }, { input: `{"severity": "foo"}`, want: SeverityUnknown, }, } for _, tt := range tests { t.Run(tt.want.String(), func(t *testing.T) { var got severityHolder err := json.Unmarshal([]byte(tt.input), &got) assert.NoError(t, err) assert.Equal(t, tt.want, got.Severity) }) } } ================================================ FILE: pkg/types/status.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "fmt" "strings" ) type CheckStatus int const ( StatusUnknown CheckStatus = iota StatusPassed StatusSkipped StatusFailed StatusError ) func (s CheckStatus) String() string { switch s { case StatusPassed: return "Passed" case StatusSkipped: return "Skipped" case StatusFailed: return "Failed" case StatusError: return "Error" default: return "" } } func ParseStatus(s string) CheckStatus { switch s { case "Passed": return StatusPassed case "Skipped": return StatusSkipped case "Failed": return StatusFailed case "Error": return StatusError default: return StatusUnknown } } func (s *CheckStatus) UnmarshalJSON(b []byte) error { *s = ParseStatus(strings.Trim(string(b), "\"")) return nil } func (s CheckStatus) MarshalJSON() ([]byte, error) { v := fmt.Sprintf(`"%s"`, s.String()) return []byte(v), nil } ================================================ FILE: pkg/types/status_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" ) type statusHolder struct { Status CheckStatus `json:"status"` } func TestCheckStatusMarshalJSON(t *testing.T) { tests := []struct { input CheckStatus want string }{ { input: StatusError, want: `{"status":"Error"}`, }, { input: StatusFailed, want: `{"status":"Failed"}`, }, { input: StatusPassed, want: `{"status":"Passed"}`, }, { input: StatusSkipped, want: `{"status":"Skipped"}`, }, } for _, tt := range tests { t.Run(tt.input.String(), func(t *testing.T) { got, err := json.Marshal(&statusHolder{tt.input}) assert.NoError(t, err) assert.Equal(t, tt.want, string(got)) }) } } func TestCheckStatusUnmarshalJSON(t *testing.T) { tests := []struct { input string want CheckStatus }{ { input: `{"status": "Passed"}`, want: StatusPassed, }, { input: `{"status": "Skipped"}`, want: StatusSkipped, }, { input: `{"status": "Failed"}`, want: StatusFailed, }, { input: `{"status": "Error"}`, want: StatusError, }, { input: `{"status": "foo"}`, want: StatusUnknown, }, } for _, tt := range tests { t.Run(tt.want.String(), func(t *testing.T) { var got statusHolder err := json.Unmarshal([]byte(tt.input), &got) assert.NoError(t, err) assert.Equal(t, tt.want, got.Status) }) } } ================================================ FILE: pkg/validator/activation.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "github.com/google/cel-go/interpreter" ) const ( ObjectVarName = "object" ParamsVarName = "params" PodMetaVarName = "podMeta" PodSpecVarName = "podSpec" AllContainersVarName = "allContainers" APIVersionsVarName = "apiVersions" KubeVersionVarName = "kubeVersion" VariableVarName = "variables" ) // activation implements the interpreter.Activation type activation struct { object map[string]any podMeta map[string]any podSpec map[string]any allContainers []map[string]any params any apiVersions []string kubeVersion any variables any } func (a *activation) ResolveName(name string) (any, bool) { switch name { case ObjectVarName: return a.object, true case PodMetaVarName: return a.podMeta, true case PodSpecVarName: return a.podSpec, true case AllContainersVarName: return a.allContainers, true case ParamsVarName: return a.params, true case APIVersionsVarName: return a.apiVersions, true case KubeVersionVarName: return a.kubeVersion, true case VariableVarName: return a.variables, true default: return nil, false } } func (a *activation) Parent() interpreter.Activation { return nil } ================================================ FILE: pkg/validator/compiler.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "errors" "fmt" "github.com/google/cel-go/cel" "github.com/google/cel-go/ext" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/version" k8scellib "k8s.io/apiserver/pkg/cel/library" "github.com/undistro/marvin/pkg/types" ) var baseEnvOptions = []cel.EnvOption{ cel.HomogeneousAggregateLiterals(), cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true), cel.CrossTypeNumericComparisons(true), cel.OptionalTypes(), cel.ASTValidators( cel.ValidateDurationLiterals(), cel.ValidateTimestampLiterals(), cel.ValidateRegexLiterals(), cel.ValidateHomogeneousAggregateLiterals(), ), ext.Strings(ext.StringsVersion(2)), ext.Sets(), ext.TwoVarComprehensions(), ext.Lists(ext.ListsVersion(3)), k8scellib.URLs(), k8scellib.Regex(), k8scellib.Lists(), k8scellib.Quantity(), k8scellib.IP(), k8scellib.CIDR(), k8scellib.Format(), k8scellib.SemverLib(k8scellib.SemverVersion(1)), cel.Variable(ObjectVarName, cel.DynType), cel.Variable(APIVersionsVarName, cel.ListType(cel.StringType)), cel.Variable(KubeVersionVarName, cel.DynType), } var programOptions = []cel.ProgramOption{ cel.EvalOptions(cel.OptOptimize), } var podSpecEnvOptions = []cel.EnvOption{ cel.Variable(PodMetaVarName, cel.DynType), cel.Variable(PodSpecVarName, cel.DynType), cel.Variable(AllContainersVarName, cel.ListType(cel.DynType)), } // Compile compiles variables and expressions of the given check and returns a Validator func Compile(check types.Check, apiResources []*metav1.APIResourceList, kubeVersion *version.Info, costLimit uint64) (Validator, error) { if len(check.Validations) == 0 { return nil, errors.New("invalid check: a check must have at least 1 validation") } env, err := newEnv(check) if err != nil { return nil, fmt.Errorf("environment construction error %s", err.Error()) } variables, err := compileVariables(env, check.Variables, costLimit) if err != nil { return nil, err } prgs, err := compileValidations(env, check.Validations, costLimit) if err != nil { return nil, err } apiVersions := make([]string, 0, len(apiResources)) for _, resource := range apiResources { apiVersions = append(apiVersions, resource.GroupVersion) } return &CELValidator{check: check, programs: prgs, apiVersions: apiVersions, kubeVersion: kubeVersion, variables: variables}, nil } func newEnv(check types.Check) (*cel.Env, error) { opts := baseEnvOptions if MatchesPodSpec(check.Match.Resources) { opts = append(opts, podSpecEnvOptions...) } if len(check.Variables) > 0 { opts = append(opts, cel.Variable(VariableVarName, cel.MapType(cel.StringType, cel.DynType))) } if len(check.Params) > 0 { opts = append(opts, cel.Variable(ParamsVarName, cel.DynType)) } return cel.NewEnv(opts...) } func compileVariables(env *cel.Env, vars []types.Variable, costLimit uint64) ([]compiledVariable, error) { variables := make([]compiledVariable, 0, len(vars)) for _, v := range vars { prg, err := compileExpression(env, v.Expression, costLimit, cel.AnyType) if err != nil { return nil, fmt.Errorf("variables[%q].expression: %s", v.Name, err) } variables = append(variables, compiledVariable{name: v.Name, program: prg}) } return variables, nil } func compileValidations(env *cel.Env, vals []types.Validation, costLimit uint64) ([]cel.Program, error) { prgs := make([]cel.Program, 0, len(vals)) for i, v := range vals { prg, err := compileExpression(env, v.Expression, costLimit, cel.BoolType) if err != nil { return nil, fmt.Errorf("validations[%d].expression: %s", i, err) } prgs = append(prgs, prg) } return prgs, nil } func compileExpression(env *cel.Env, exp string, costLimit uint64, allowedTypes ...*cel.Type) (cel.Program, error) { ast, issues := env.Compile(exp) if issues != nil && issues.Err() != nil { return nil, fmt.Errorf("type-check error: %s", issues.Err()) } found := false for _, t := range allowedTypes { if ast.OutputType() == t || cel.AnyType == t { found = true break } } if !found { if len(allowedTypes) == 1 { return nil, fmt.Errorf("must evaluate to %v", allowedTypes[0].String()) } return nil, fmt.Errorf("must evaluate to one of %v", allowedTypes) } opts := programOptions if costLimit > 0 { opts = append(opts, cel.CostLimit(costLimit)) } prg, err := env.Program(ast, opts...) if err != nil { return nil, fmt.Errorf("program construction error: %s", err) } return prg, nil } ================================================ FILE: pkg/validator/compiler_test.go ================================================ // Copyright 2024 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/undistro/marvin/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/version" ) func TestCompile(t *testing.T) { var apiResources []*metav1.APIResourceList kubeVersion := &version.Info{Major: "1", Minor: "29", GitVersion: "v1.29.2"} podsMatch := types.Match{Resources: []types.ResourceRule{{ Group: "", Version: "v1", Resource: "pods", }}} tests := []struct { check types.Check wantErr assert.ErrorAssertionFunc }{ { check: types.Check{ ID: "ok", Match: podsMatch, Validations: []types.Validation{{ Expression: `variables.isWindows || allContainers.size() > 0`, }}, Variables: []types.Variable{{ Name: "isWindows", Expression: `podSpec.?os.?name.orValue("") == "windows"`, }}, }, wantErr: assert.NoError, }, { check: types.Check{ ID: "validation error", Match: podsMatch, Validations: []types.Validation{{ Expression: `allContainers.sizeX() > 0`, }}, }, wantErr: assert.Error, }, { check: types.Check{ ID: "variable error", Match: podsMatch, Validations: []types.Validation{{ Expression: `variables.isWindows || allContainers.size() > 0`, }}, Variables: []types.Variable{{ Name: "isWindows", Expression: `foo`, }}, }, wantErr: assert.Error, }, { check: types.Check{ ID: "no workload", Match: types.Match{Resources: []types.ResourceRule{{ Group: "", Version: "v1", Resource: "configmaps", }}}, Validations: []types.Validation{{ Expression: `allContainers.size() > 0`, }}, }, wantErr: assert.Error, }, { check: types.Check{ ID: "no validations", Match: podsMatch, Validations: nil, Variables: []types.Variable{{ Name: "isWindows", Expression: `podSpec.?os.?name.orValue("") == "windows"`, }}, }, wantErr: assert.Error, }, } for _, tt := range tests { t.Run(tt.check.ID, func(t *testing.T) { _, err := Compile(tt.check, apiResources, kubeVersion, 1000000) if !tt.wantErr(t, err, fmt.Sprintf("Compile(%v, %v, %v)", tt.check, apiResources, kubeVersion)) { return } }) } } ================================================ FILE: pkg/validator/interface.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/version" ) // Validator is an interface that defines a method for validating a k8s unustructured object type Validator interface { Validate(obj unstructured.Unstructured, params any) (bool, string, error) SetAPIVersions(apiVersions []string) SetKubeVersion(v *version.Info) } ================================================ FILE: pkg/validator/podspec.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "fmt" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "github.com/undistro/marvin/pkg/types" ) // MatchesPodSpec returns true if any rule matches a Pod spec func MatchesPodSpec(rules []types.ResourceRule) bool { for _, r := range rules { gr := r.ToGVR().GroupResource() if defaultPodSpecResources[gr] { return true } } return false } var defaultPodSpecResources = map[schema.GroupResource]bool{ corev1.Resource("pods"): true, corev1.Resource("replicationcontrollers"): true, corev1.Resource("podtemplates"): true, appsv1.Resource("replicasets"): true, appsv1.Resource("deployments"): true, appsv1.Resource("statefulsets"): true, appsv1.Resource("daemonsets"): true, batchv1.Resource("jobs"): true, batchv1.Resource("cronjobs"): true, } // HasPodSpec returns true if the given object has a Pod spec func HasPodSpec(u unstructured.Unstructured) bool { gk := u.GroupVersionKind().GroupKind() _, ok := defaultPodSpecTypes[gk] return ok } // ExtractPodSpec returns the metadata and Pod spec from the given object func ExtractPodSpec(u unstructured.Unstructured) (*metav1.ObjectMeta, *corev1.PodSpec, error) { gk := u.GroupVersionKind().GroupKind() obj, ok := defaultPodSpecTypes[gk] if !ok { return nil, nil, fmt.Errorf("unexpected object type: %s", u.GetObjectKind().GroupVersionKind().String()) } err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), obj) if err != nil { return nil, nil, fmt.Errorf("from unstructured converter error: %s", err.Error()) } switch o := obj.(type) { case *corev1.Pod: return &o.ObjectMeta, &o.Spec, nil case *corev1.PodTemplate: return extractPodSpecFromTemplate(&o.Template) case *corev1.ReplicationController: return extractPodSpecFromTemplate(o.Spec.Template) case *appsv1.ReplicaSet: return extractPodSpecFromTemplate(&o.Spec.Template) case *appsv1.Deployment: return extractPodSpecFromTemplate(&o.Spec.Template) case *appsv1.DaemonSet: return extractPodSpecFromTemplate(&o.Spec.Template) case *appsv1.StatefulSet: return extractPodSpecFromTemplate(&o.Spec.Template) case *batchv1.Job: return extractPodSpecFromTemplate(&o.Spec.Template) case *batchv1.CronJob: return extractPodSpecFromTemplate(&o.Spec.JobTemplate.Spec.Template) default: return nil, nil, fmt.Errorf("unexpected object type: %s", u.GetObjectKind().GroupVersionKind().String()) } } var defaultPodSpecTypes = map[schema.GroupKind]any{ corev1.SchemeGroupVersion.WithKind("Pod").GroupKind(): &corev1.Pod{}, corev1.SchemeGroupVersion.WithKind("ReplicationController").GroupKind(): &corev1.ReplicationController{}, corev1.SchemeGroupVersion.WithKind("PodTemplate").GroupKind(): &corev1.PodTemplate{}, appsv1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind(): &appsv1.ReplicaSet{}, appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind(): &appsv1.Deployment{}, appsv1.SchemeGroupVersion.WithKind("StatefulSet").GroupKind(): &appsv1.StatefulSet{}, appsv1.SchemeGroupVersion.WithKind("DaemonSet").GroupKind(): &appsv1.DaemonSet{}, batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(): &batchv1.Job{}, batchv1.SchemeGroupVersion.WithKind("CronJob").GroupKind(): &batchv1.CronJob{}, } func extractPodSpecFromTemplate(template *corev1.PodTemplateSpec) (*metav1.ObjectMeta, *corev1.PodSpec, error) { if template == nil { return nil, nil, nil } return &template.ObjectMeta, &template.Spec, nil } func extractAllContainers(podSpec *corev1.PodSpec) []corev1.Container { containers := append(podSpec.Containers, podSpec.InitContainers...) for _, ec := range podSpec.EphemeralContainers { c := corev1.Container{ Name: ec.Name, Image: ec.Image, Command: ec.Command, Args: ec.Args, WorkingDir: ec.WorkingDir, Ports: ec.Ports, EnvFrom: ec.EnvFrom, Env: ec.Env, Resources: ec.Resources, VolumeMounts: ec.VolumeMounts, VolumeDevices: ec.VolumeDevices, LivenessProbe: ec.LivenessProbe, ReadinessProbe: ec.ReadinessProbe, StartupProbe: ec.StartupProbe, Lifecycle: ec.Lifecycle, TerminationMessagePath: ec.TerminationMessagePath, TerminationMessagePolicy: ec.TerminationMessagePolicy, ImagePullPolicy: ec.ImagePullPolicy, SecurityContext: ec.SecurityContext, Stdin: ec.Stdin, StdinOnce: ec.StdinOnce, TTY: ec.TTY, } containers = append(containers, c) } return containers } ================================================ FILE: pkg/validator/podspec_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "testing" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/undistro/marvin/pkg/types" ) func TestMatchesPodSpec(t *testing.T) { tests := []struct { name string rules []types.ResourceRule want bool }{ { name: "deployments", rules: []types.ResourceRule{{ Group: "apps", Version: "v1", Resource: "deployments", }}, want: true, }, { name: "pods and services", rules: []types.ResourceRule{ { Group: "", Version: "v1", Resource: "pods", }, { Group: "", Version: "v1", Resource: "services", }, }, want: true, }, { name: "services and cronjobs", rules: []types.ResourceRule{ { Group: "", Version: "v1", Resource: "services", }, { Group: "batch", Version: "v1", Resource: "cronjobs", }, }, want: true, }, { name: "services", rules: []types.ResourceRule{{ Group: "", Version: "v1", Resource: "services", }}, want: false, }, { name: "empty", rules: []types.ResourceRule{}, want: false, }, { name: "nil", rules: nil, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := MatchesPodSpec(tt.rules); got != tt.want { t.Errorf("MatchesPodSpec() = %v, want %v", got, tt.want) } }) } } func TestExtractPodSpec(t *testing.T) { metadata := map[string]any{ "name": "foo-pod", } expectedMeta := &v1.ObjectMeta{ Name: "foo-pod", } spec := map[string]any{ "containers": []map[string]any{{ "name": "foo-container", }}, } expectedSpec := &corev1.PodSpec{ Containers: []corev1.Container{{Name: "foo-container"}}, } objects := []map[string]any{ { "apiVersion": "v1", "kind": "Pod", "metadata": metadata, "spec": spec, }, { "apiVersion": "v1", "kind": "PodTemplate", "metadata": map[string]any{"name": "foo-template"}, "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, { "apiVersion": "v1", "kind": "ReplicationController", "metadata": map[string]any{"name": "foo-rc"}, "spec": map[string]any{ "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, }, { "apiVersion": "apps/v1", "kind": "ReplicaSet", "metadata": map[string]any{"name": "foo-rs"}, "spec": map[string]any{ "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]any{"name": "foo-deployment"}, "spec": map[string]any{ "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, }, { "apiVersion": "apps/v1", "kind": "StatefulSet", "metadata": map[string]any{"name": "foo-ss"}, "spec": map[string]any{ "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": map[string]any{"name": "foo-ds"}, "spec": map[string]any{ "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, }, { "apiVersion": "batch/v1", "kind": "Job", "metadata": map[string]any{"name": "foo-job"}, "spec": map[string]any{ "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, }, { "apiVersion": "batch/v1", "kind": "CronJob", "metadata": map[string]any{"name": "foo-cronjob"}, "spec": map[string]any{ "jobTemplate": map[string]any{ "spec": map[string]any{ "template": map[string]any{ "metadata": metadata, "spec": spec, }, }, }, }, }, } for _, obj := range objects { u := unstructured.Unstructured{Object: obj} name := u.GetName() actualMetadata, actualSpec, err := ExtractPodSpec(u) assert.NoError(t, err, name) assert.Equal(t, expectedMeta, actualMetadata, "%s: Metadata mismatch", name) assert.Equal(t, expectedSpec, actualSpec, "%s: PodSpec mismatch", name) } var service = map[string]any{ "apiVersion": "v1", "kind": "Service", "metadata": map[string]any{ "name": "foo-svc", }, } _, _, err := ExtractPodSpec(unstructured.Unstructured{Object: service}) assert.Error(t, err, "service should not have an extractable pod spec") } func TestExtractAllContainers(t *testing.T) { tests := []struct { name string podSpec *corev1.PodSpec want []corev1.Container }{ { name: "single container", podSpec: &corev1.PodSpec{Containers: []corev1.Container{{Name: "foo"}}}, want: []corev1.Container{{Name: "foo"}}, }, { name: "init container", podSpec: &corev1.PodSpec{ Containers: []corev1.Container{{Name: "foo"}}, InitContainers: []corev1.Container{{Name: "init"}}, }, want: []corev1.Container{ {Name: "foo"}, {Name: "init"}, }, }, { name: "init container and sidecar", podSpec: &corev1.PodSpec{ Containers: []corev1.Container{{Name: "proxy"}, {Name: "foo"}}, InitContainers: []corev1.Container{{Name: "init"}}, }, want: []corev1.Container{ {Name: "proxy"}, {Name: "foo"}, {Name: "init"}, }, }, { name: "ephemeral container", podSpec: &corev1.PodSpec{ Containers: []corev1.Container{{Name: "proxy"}, {Name: "foo"}}, InitContainers: []corev1.Container{{Name: "init"}}, EphemeralContainers: []corev1.EphemeralContainer{{EphemeralContainerCommon: corev1.EphemeralContainerCommon{Name: "ephemeral"}}}, }, want: []corev1.Container{ {Name: "proxy"}, {Name: "foo"}, {Name: "init"}, {Name: "ephemeral"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equalf(t, tt.want, extractAllContainers(tt.podSpec), "extractAllContainers(%v)", tt.podSpec) }) } } ================================================ FILE: pkg/validator/validator.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "fmt" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" marvin "github.com/undistro/marvin/pkg/types" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/version" "k8s.io/apiserver/pkg/cel/lazy" ) // CELValidator is a Validator that performs CEL expressions type CELValidator struct { check marvin.Check programs []cel.Program apiVersions []string kubeVersion *version.Info variables []compiledVariable } type compiledVariable struct { name string program cel.Program } func (r *CELValidator) SetAPIVersions(apiVersions []string) { r.apiVersions = apiVersions } func (r *CELValidator) SetKubeVersion(v *version.Info) { r.kubeVersion = v } func (r *CELValidator) Validate(obj unstructured.Unstructured, params any) (bool, string, error) { if params == nil { params = r.check.Params } input := &activation{object: obj.UnstructuredContent(), apiVersions: r.apiVersions, params: params, variables: make(map[string]any)} if err := r.setPodSpecParams(obj, input); err != nil { return false, "", err } lazyMap := lazy.NewMapValue(types.MapType) for _, v := range r.variables { lazyMap.Append(v.name, callback(v, input)) } input.variables = lazyMap for i, prg := range r.programs { out, _, err := prg.Eval(input) if err != nil { return false, "", fmt.Errorf("evaluate error: %s", err) } if out != types.True { return false, r.check.Validations[i].Message, nil } } return true, "", nil } func callback(v compiledVariable, activation any) lazy.GetFieldFunc { return func(_ *lazy.MapValue) ref.Val { val, _, err := v.program.Eval(activation) if err != nil { return types.NewErr("variable %q fails to evaluate: %v", v.name, err) } return val } } func (r *CELValidator) setPodSpecParams(obj unstructured.Unstructured, input *activation) error { if !HasPodSpec(obj) { return nil } meta, spec, err := ExtractPodSpec(obj) if err != nil { return fmt.Errorf("pod spec extract error: %s", err) } podSpec, err := runtime.DefaultUnstructuredConverter.ToUnstructured(spec) if err != nil { return fmt.Errorf("podSpec to unstructured converter error: %s", err.Error()) } podMeta, err := runtime.DefaultUnstructuredConverter.ToUnstructured(meta) if err != nil { return fmt.Errorf("podMeta to unstructured converter error: %s", err.Error()) } input.podSpec = podSpec input.podMeta = podMeta for _, container := range extractAllContainers(spec) { c, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&container) if err != nil { return fmt.Errorf("container to unstructured converter error: %s", err.Error()) } input.allContainers = append(input.allContainers, c) } return nil } ================================================ FILE: pkg/version/version.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package version import ( "github.com/Masterminds/semver/v3" ) var ( version = "dev" commit = "" date = "" ) type Info struct { Version string `json:"version"` Major uint64 `json:"major"` Minor uint64 `json:"minor"` Commit string `json:"commit"` Date string `json:"date"` } func (i Info) String() string { return i.Version } func Get() Info { i := &Info{ Version: version, Commit: commit, Date: date, } v, err := semver.NewVersion(version) if err == nil { i.Major = v.Major() i.Minor = v.Minor() } return *i } ================================================ FILE: pkg/version/version_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package version import ( "reflect" "testing" ) func TestGet(t *testing.T) { type args struct { version string commit string date string } tests := []struct { name string args *args want Info }{ { name: "default", want: Info{ Version: "dev", Commit: "", Date: "", Major: 0, Minor: 0, }, }, { name: "version", args: &args{ version: "0.1.0", commit: "commit", date: "date", }, want: Info{ Version: "0.1.0", Major: 0, Minor: 1, Commit: "commit", Date: "date", }, }, { name: "prefixed", args: &args{ version: "v0.1.0", commit: "commit", date: "date", }, want: Info{ Version: "v0.1.0", Major: 0, Minor: 1, Commit: "commit", Date: "date", }, }, { name: "pre-release", args: &args{ version: "v0.1.1-next", commit: "commit", date: "date", }, want: Info{ Version: "v0.1.1-next", Major: 0, Minor: 1, Commit: "commit", Date: "date", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.args != nil { version = tt.args.version commit = tt.args.commit date = tt.args.date } got := Get() if !reflect.DeepEqual(got, tt.want) { t.Errorf("Get() = %v, want %v", got, tt.want) } if got.String() != tt.want.Version { t.Errorf("String() = %v, want %v", got.String(), tt.want.Version) } }) } } ================================================ FILE: test/builtins_test.go ================================================ // Copyright 2023 Undistro Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package test import ( "testing" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" "github.com/undistro/marvin/pkg/loader" "github.com/undistro/marvin/pkg/validator" ) func TestBuiltinChecks(t *testing.T) { checks, tests, err := loader.LoadChecksAndTests("../internal/builtins/") assert.NoError(t, err) assert.NotEmpty(t, checks) assert.GreaterOrEqual(t, len(checks), len(tests)) for path, checkTests := range tests { t.Run(path, func(t *testing.T) { check, ok := checks[path] assert.True(t, ok) assert.NotNil(t, check) assert.NotEmpty(t, check.ID) v, err := validator.Compile(check, nil, nil, 1000000) assert.NoError(t, err) assert.NotNil(t, v) for _, tt := range checkTests { t.Run(tt.Name, func(t *testing.T) { obj, err := parse(tt.Input) assert.NoError(t, err) assert.NotNil(t, obj) v.SetAPIVersions(tt.APIVersions) v.SetKubeVersion(tt.KubeVersion) got, msg, err := v.Validate(obj, tt.Params) assert.NoError(t, err) assert.Equal(t, tt.Pass, got) assert.Equal(t, tt.Message, msg) }) } }) } } func parse(i string) (unstructured.Unstructured, error) { var obj unstructured.Unstructured err := yaml.Unmarshal([]byte(i), &obj) return obj, err }