├── .conform.yaml ├── .dockerignore ├── .editorconfig ├── .gcloudignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── dependabot.yml └── workflows │ ├── lint_go.yml │ ├── release.yml │ ├── release_containers.yml │ ├── security_analysis.yml │ ├── test_acceptance.yml │ └── test_unit.yml ├── .gitignore ├── .gitmodules ├── .goreleaser.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.alpine ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── SECURITY.md ├── cmd ├── main.go ├── scan.go └── version.go ├── doc └── images │ └── badrobot_logo.png ├── go.mod ├── go.sum ├── main.go ├── pkg ├── report │ └── writer.go ├── ruler │ ├── report.go │ ├── rule.go │ ├── rule_test.go │ ├── ruleset.go │ └── ruleset_test.go └── rules │ ├── admissionControllerClusterRole.go │ ├── admissionControllerClusterRole_test.go │ ├── allowPrivilegeEscalation.go │ ├── allowPrivilegeEscalation_test.go │ ├── bindClusterRole.go │ ├── bindClusterRole_test.go │ ├── capSysAdmin.go │ ├── capSysAdmin_test.go │ ├── clusterAdmin.go │ ├── clusterAdmin_test.go │ ├── customResourceClusterRole.go │ ├── customResourceClusterRole_test.go │ ├── defaultNamespace.go │ ├── defaultNamespace_test.go │ ├── escalateClusterRole.go │ ├── escalateClusterRole_test.go │ ├── execPodsClusterRole.go │ ├── execPodsClusterRole_test.go │ ├── impersonateClusterRole.go │ ├── impersonateClusterRole_test.go │ ├── kubesystemNamespace.go │ ├── kubesystemNamespace_test.go │ ├── modifyPodLogsClusterRole.go │ ├── modifyPodLogsClusterRole_test.go │ ├── networkPolicyClusterRole.go │ ├── networkPolicyClusterRole_test.go │ ├── noSecurityContext.go │ ├── noSecurityContext_test.go │ ├── nodeProxyClusterRole.go │ ├── nodeProxyClusterRole_test.go │ ├── persistentVolumesClusterRole.go │ ├── persistentVolumesClusterRole_test.go │ ├── privileged.go │ ├── privileged_test.go │ ├── readOnlyRootFilesystem.go │ ├── readOnlyRootFilesystem_test.go │ ├── removeEventsClusterRole.go │ ├── removeEventsClusterRole_test.go │ ├── runAsNonRoot.go │ ├── runAsNonRoot_test.go │ ├── runAsUser.go │ ├── runAsUser_test.go │ ├── secretsClusterRole.go │ ├── secretsClusterRole_test.go │ ├── selector.go │ ├── serviceAccountClusterRole.go │ ├── serviceAccountClusterRole_test.go │ ├── slice.go │ ├── starAllClusterRole.go │ ├── starAllClusterRole_test.go │ ├── starAllCoreAPIClusterRole.go │ ├── starAllCoreAPIClusterRole_test.go │ ├── starClusterRoleAndBindings.go │ └── starClusterRoleAndBindings_test.go └── test ├── 0_test_deps.bats ├── 1_cli.bats ├── 2_regression.bats ├── _helper.bash └── asset ├── cr-ad-both-star.yaml ├── cr-ad-both-verbs.yaml ├── cr-ad-mutating-webhook-star.yaml ├── cr-ad-mutating-webhook-verbs.yaml ├── cr-ad-validating-webhook-star.yaml ├── cr-ad-validating-webhook-verbs.yaml ├── cr-all-clusterrolebindings-only.yaml ├── cr-all-clusterroles-only.yaml ├── cr-all-clusterrolesandbindings-star.yaml ├── cr-all-clusterrolesandbindings-verbs.yaml ├── cr-all-crbs-separate.yaml ├── cr-all-star.yaml ├── cr-all-verbs.yaml ├── cr-bind.yaml ├── cr-coreapi-all-verbs.yaml ├── cr-coreapi-limited-resources.yaml ├── cr-coreapi-limited-verbs.yaml ├── cr-coreapi-star.yaml ├── cr-custom-resource-star.yaml ├── cr-custom-resource-verbs.yaml ├── cr-escalate.yaml ├── cr-impersonate.yaml ├── cr-network-get.yaml ├── cr-network-policy-get.yaml ├── cr-network-policy-star.yaml ├── cr-network-policy-verbs.yaml ├── cr-network-star.yaml ├── cr-network-verbs.yaml ├── cr-node-proxy-star.yaml ├── cr-node-proxy-verbs.yaml ├── cr-node-proxy-watch.yaml ├── cr-node-star.yaml ├── cr-node-verbs.yaml ├── cr-node-watch.yaml ├── cr-noncoreapi-limited.yaml ├── cr-noncoreapi-star.yaml ├── cr-pods-podsexec-combined-star.yaml ├── cr-pods-podsexec-combined-verbs.yaml ├── cr-pods-podsexec-exact-verbs.yaml ├── cr-pods-podsexec-incorrect-verbs.yaml ├── cr-pods-podsexec-star.yaml ├── cr-pods-podsexec-verbs.yaml ├── cr-pods-star-podsexec-verbs.yaml ├── cr-pods-star.yaml ├── cr-pods-verbs-podsexec-star.yaml ├── cr-pods-verbs.yaml ├── cr-podsexec-star.yaml ├── cr-podsexec-verbs.yaml ├── cr-podslog-single-verb.yaml ├── cr-podslog-star.yaml ├── cr-podslog-verbs.yaml ├── cr-pvc-separate-resources.yaml ├── cr-pvc-star.yaml ├── cr-pvc-verbs.yaml ├── cr-remove-events-star.yaml ├── cr-remove-events-verbs.yaml ├── cr-sa-only-get.yaml ├── cr-sa-star.yaml ├── cr-sa-token-only-get.yaml ├── cr-sa-token-star.yaml ├── cr-sa-token-verbs.yaml ├── cr-sa-verbs.yaml ├── cr-secrets-all-verbs.yaml ├── cr-secrets-star.yaml ├── crb-cluster-admin-suffix.yaml ├── crb-cluster-admin.yaml ├── crb-dedicated-cr.yaml ├── crb-ns-dedicated.yaml ├── crb-ns-default.yaml ├── crb-ns-kubesystem.yaml ├── crb-prefix-cluster-admin.yaml ├── deploy-ns-dedicated.yaml ├── deploy-ns-default.yaml ├── deploy-ns-kubesystem.yaml ├── deploy-sc-both-all.yaml ├── deploy-sc-containers-all.yaml ├── deploy-sc-containers-allow-priv.yaml ├── deploy-sc-containers-cap-sysadmin.yaml ├── deploy-sc-containers-nonroot.yaml ├── deploy-sc-containers-priv.yaml ├── deploy-sc-containers-readonly-root.yaml ├── deploy-sc-containers-rootuser.yaml ├── deploy-sc-none.yaml ├── deploy-sc-spec-all.yaml ├── deploy-sc-spec-allow-priv.yaml ├── deploy-sc-spec-cap-sysadmin.yaml ├── deploy-sc-spec-nonroot.yaml ├── deploy-sc-spec-priv.yaml ├── deploy-sc-spec-readonly-root.yaml ├── deploy-sc-spec-rootuser.yaml ├── ns-dedicated.yaml ├── ns-default.yaml ├── ns-kubesystem.yaml ├── reg-empty-file ├── reg-empty-json-file ├── reg-invalid-kind.yaml └── reg-invalid-schema.yaml /.conform.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | policies: 3 | - type: commit 4 | spec: 5 | header: 6 | length: 89 7 | imperative: true 8 | case: lower 9 | invalidLastCharacters: . 10 | body: 11 | required: true 12 | dco: false 13 | gpg: true 14 | spellcheck: 15 | locale: GB 16 | conventional: 17 | types: 18 | - chore 19 | - docs 20 | - feat 21 | - refactor 22 | - style 23 | - fix 24 | - test 25 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile* 2 | Jenkinsfile* 3 | **/.terraform 4 | .git/ 5 | dist 6 | 7 | .idea/ 8 | *.iml 9 | .gcloudignore 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | insert_final_newline = true 8 | max_line_length = 120 9 | trim_trailing_whitespace = true 10 | 11 | [Dockerfile*] 12 | indent_size = 4 13 | 14 | [{Makefile,makefile,**.mk}] 15 | indent_style = tab 16 | 17 | [*.sh] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | shell_variant = bash # like -ln=posix 22 | binary_next_line = true # like -bn 23 | switch_case_indent = true # like -ci 24 | space_redirects = true # like -sr 25 | keep_padding = false # like -kp 26 | 27 | -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | .gcloudignore 2 | # If you would like to upload your .git directory, .gitignore file or 3 | # files from your .gitignore file, remove the corresponding line below: 4 | .git 5 | .gitignore 6 | #!include:.gitignore 7 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global Repository Owners 2 | * @wakeward @06kellyjac 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behaviour: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behaviour** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. 11 | For example: I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Post a question about the project 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | **Your question** 10 | A clear and concise question. 11 | 12 | **Additional context** 13 | Add any other context about your question here. 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pull Request 3 | about: A pull request 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | [pull_requests]: https://github.com/controlplaneio/badrobot/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc 10 | 11 | 12 | 13 | **All Submissions.** 14 | 15 | - [ ] Have you followed the guidelines in our [Contributing document](../../CONTRIBUTING.md)? 16 | - [ ] Have you checked to ensure there aren't other open [Pull Requests][pull_requests] for the same update/change? 17 | 18 | **Code Submissions.** 19 | 20 | - [ ] Does your submission pass linting, tests, and security analysis? 21 | 22 | **Changes to Core Features.** 23 | 24 | - [ ] Have you added an explanation of what your changes do and why you'd like us to include them? 25 | - [ ] Have you written new tests for your core changes, as applicable? 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: weekly 8 | # offset from the hour to avoid other build jobs 9 | time: "06:34" 10 | timezone: Etc/UTC 11 | open-pull-requests-limit: 10 12 | commit-message: 13 | prefix: chore 14 | include: scope 15 | groups: 16 | gha: 17 | patterns: 18 | - "*" 19 | - package-ecosystem: gomod 20 | directory: / 21 | schedule: 22 | interval: weekly 23 | # offset from the hour to avoid other build jobs 24 | time: "06:34" 25 | timezone: Etc/UTC 26 | allow: 27 | # direct and indirect updates 28 | - dependency-type: "all" 29 | commit-message: 30 | prefix: chore 31 | include: scope 32 | groups: 33 | gomod: 34 | patterns: 35 | - "*" 36 | - package-ecosystem: docker 37 | directory: / 38 | schedule: 39 | interval: weekly 40 | # offset from the hour to avoid other build jobs 41 | time: "06:34" 42 | timezone: Etc/UTC 43 | commit-message: 44 | prefix: chore 45 | include: scope 46 | groups: 47 | docker: 48 | patterns: 49 | - "*" 50 | -------------------------------------------------------------------------------- /.github/workflows/lint_go.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Linting - Go 3 | # Split until path filtering for jobs added 4 | # https://github.community/t/path-filtering-for-jobs-and-steps/16447 5 | on: 6 | push: 7 | branches: [master] 8 | paths: 9 | - "**.go" 10 | - "go.mod" 11 | - "go.sum" 12 | pull_request: 13 | branches: [master] 14 | paths: 15 | - "**.go" 16 | - "go.mod" 17 | - "go.sum" 18 | 19 | jobs: 20 | golangci-lint: 21 | name: golangci-lint 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Run golangci-lint 28 | uses: reviewdog/action-golangci-lint@v2 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | on: 4 | # https://github.com/actions/runner/issues/1007 5 | push: 6 | tags: 7 | - "v[0-9]+.[0-9]+.[0-9]+" 8 | 9 | jobs: 10 | release: 11 | name: Release on GitHub 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version-file: go.mod 21 | 22 | - name: Launch goreleaser 23 | uses: goreleaser/goreleaser-action@v6.3.0 24 | with: 25 | args: release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/release_containers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Containers 3 | on: 4 | # https://github.com/actions/runner/issues/1007 5 | push: 6 | tags: 7 | - "v[0-9]+.[0-9]+.[0-9]+" 8 | 9 | jobs: 10 | release-containers: 11 | name: Build and Push container - ${{ matrix.containers.name }} 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | containers: 16 | - name: badrobot 17 | file: ./Dockerfile 18 | suffix: "" 19 | - name: badrobot alpine 20 | file: ./Dockerfile.alpine 21 | suffix: -alpine 22 | 23 | steps: 24 | - name: Cache container layers 25 | uses: actions/cache@v4.2.3 26 | with: 27 | path: /tmp/.buildx-cache 28 | key: ${{ runner.os }}${{ matrix.containers.suffix }}-buildx-${{ github.sha }} 29 | restore-keys: | 30 | ${{ runner.os }}${{ matrix.containers.suffix }}-buildx- 31 | 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | 35 | - name: Generate container tags and labels 36 | id: docker_meta 37 | uses: docker/metadata-action@v5 38 | with: 39 | images: controlplane/badrobot 40 | tags: | 41 | type=semver,pattern=v{{version}} 42 | type=semver,pattern=v{{major}} 43 | flavor: | 44 | latest=${{ matrix.containers.suffix == '' }} 45 | suffix=${{ matrix.containers.suffix }} 46 | labels: | 47 | org.opencontainers.image.vendor=controlplane 48 | 49 | - name: Login to Docker Hub Registry 50 | uses: docker/login-action@v3.4.0 51 | with: 52 | registry: docker.io 53 | username: ${{ secrets.DOCKERHUB_USERNAME }} 54 | password: ${{ secrets.DOCKERHUB_TOKEN }} 55 | 56 | # - name: Login to GitHub Container Registry 57 | # uses: docker/login-action@v3.4.0 58 | # with: 59 | # registry: ghcr.io 60 | # username: ${{ github.repository_owner }} 61 | # password: ${{ secrets.CR_PAT }} 62 | 63 | - name: Set up QEMU 64 | uses: docker/setup-qemu-action@v3 65 | 66 | - name: Set up Docker buildx 67 | uses: docker/setup-buildx-action@v3 68 | 69 | - name: Build container and push tags 70 | uses: docker/build-push-action@v6 71 | with: 72 | context: . 73 | platforms: linux/amd64,linux/arm64 74 | push: ${{ github.event_name != 'pull_request' }} 75 | file: ${{ matrix.containers.file }} 76 | cache-from: type=local,src=/tmp/.buildx-cache 77 | cache-to: mode=max,type=local,dest=/tmp/.buildx-cache 78 | tags: ${{ steps.docker_meta.outputs.tags }} 79 | labels: ${{ steps.docker_meta.outputs.labels }} 80 | build-args: | 81 | VERSION=${{ fromJSON(steps.docker_meta.outputs.json).labels['org.opencontainers.image.version'] }} 82 | COMMIT=${{ fromJSON(steps.docker_meta.outputs.json).labels['org.opencontainers.image.revision'] }} 83 | -------------------------------------------------------------------------------- /.github/workflows/security_analysis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security Analysis 3 | 4 | on: 5 | push: 6 | branches: [master] 7 | paths: 8 | - "**.go" 9 | - "go.mod" 10 | - "go.sum" 11 | pull_request: 12 | branches: [master] 13 | paths: 14 | - "**.go" 15 | - "go.mod" 16 | - "go.sum" 17 | schedule: 18 | # 06:12 - offset from the hour to avoid other build jobs 19 | - cron: "12 6 * * *" 20 | 21 | jobs: 22 | codeql: 23 | name: CodeQL 24 | runs-on: ubuntu-latest 25 | permissions: 26 | # allow uploading sarif results 27 | security-events: write 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Initialize CodeQL 33 | uses: github/codeql-action/init@v3 34 | with: 35 | languages: go 36 | 37 | # analyzes + uploads sarif 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | 41 | govulncheck: 42 | name: govulncheck 43 | runs-on: ubuntu-latest 44 | permissions: 45 | # allow uploading sarif results 46 | security-events: write 47 | steps: 48 | - name: Checkout repository 49 | uses: actions/checkout@v4 50 | 51 | - name: Vulnerability Scan Go Code 52 | uses: golang/govulncheck-action@v1 53 | with: 54 | go-version-file: go.mod 55 | repo-checkout: false 56 | output-format: sarif 57 | output-file: govulncheck.sarif 58 | 59 | - name: Fix govulncheck SARIF output 60 | # https://github.com/docker/buildx/blob/d4eca07af8385dca95b4c38535a9bbaa3bfc0fa9/hack/dockerfiles/govulncheck.Dockerfile#L22-L25 61 | # Make sure "results" field is defined in SARIF output otherwise GitHub Code Scanning 62 | # will fail when uploading report with "Invalid SARIF. Missing 'results' array in run." 63 | # Relates to https://github.com/golang/vuln/blob/ffdef74cc44d7eb71931d8d414c478b966812488/internal/sarif/sarif.go#L69 64 | run: | 65 | cat <<< $(jq '(.runs[] | select(.results == null) | .results) |= []' govulncheck.sarif) > govulncheck.sarif 66 | 67 | - name: Upload Scan SARIF file 68 | uses: github/codeql-action/upload-sarif@v3 69 | with: 70 | sarif_file: govulncheck.sarif 71 | category: govulncheck 72 | -------------------------------------------------------------------------------- /.github/workflows/test_acceptance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Testing - Acceptance 3 | # Split until path filtering for jobs added 4 | # https://github.community/t/path-filtering-for-jobs-and-steps/16447 5 | on: 6 | push: 7 | branches: [master] 8 | paths: 9 | - "**.go" 10 | - "go.mod" 11 | - "go.sum" 12 | - "**.bash" 13 | - "**.bats" 14 | pull_request: 15 | branches: [master] 16 | paths: 17 | - "**.go" 18 | - "go.mod" 19 | - "go.sum" 20 | - "**.bash" 21 | - "**.bats" 22 | 23 | jobs: 24 | acceptance: 25 | name: Bats acceptance tests 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v4 30 | with: 31 | # needed for bats tests 32 | submodules: true 33 | 34 | - name: Set up Go 35 | uses: actions/setup-go@v5 36 | id: go 37 | with: 38 | go-version-file: go.mod 39 | 40 | - name: Run bats acceptance tests 41 | run: | 42 | make build test-acceptance 43 | -------------------------------------------------------------------------------- /.github/workflows/test_unit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Testing - Unit 3 | # Split until path filtering for jobs added 4 | # https://github.community/t/path-filtering-for-jobs-and-steps/16447 5 | on: 6 | push: 7 | branches: [master] 8 | paths: 9 | - "**.go" 10 | - "go.mod" 11 | - "go.sum" 12 | pull_request: 13 | branches: [master] 14 | paths: 15 | - "**.go" 16 | - "go.mod" 17 | - "go.sum" 18 | 19 | jobs: 20 | unit: 21 | name: Go Unit Tests 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Set up Go 28 | uses: actions/setup-go@v5 29 | with: 30 | go-version-file: go.mod 31 | 32 | - name: Run go unit tests 33 | run: | 34 | make test-unit 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Secrets # 2 | ########### 3 | *.pem 4 | *.key 5 | *_rsa 6 | 7 | # Compiled source # 8 | ################### 9 | *.com 10 | *.class 11 | *.dll 12 | *.exe 13 | *.o 14 | *.so 15 | *.pyc 16 | 17 | # Packages # 18 | ############ 19 | # it's better to unpack these files and commit the raw source 20 | # git has its own built in compression methods 21 | *.7z 22 | *.dmg 23 | *.gz 24 | *.iso 25 | *.jar 26 | *.rar 27 | *.tar 28 | *.zip 29 | 30 | # Logs and databases # 31 | ###################### 32 | *.log 33 | *.sqlite 34 | pip-log.txt 35 | 36 | # OS generated files # 37 | ###################### 38 | .DS_Store? 39 | ehthumbs.db 40 | Icon? 41 | Thumbs.db 42 | 43 | # IDE generated files # 44 | ####################### 45 | .idea/ 46 | *.iml 47 | atlassian-ide-plugin.xml 48 | .vscode 49 | 50 | # Test Files # 51 | ############## 52 | test/log 53 | .coverage 54 | .tox 55 | nosetests.xml 56 | 57 | # Package Managed Files # 58 | ######################### 59 | bower_components/ 60 | vendor/ 61 | composer.lock 62 | node_modules/ 63 | .npm/ 64 | venv/ 65 | .venv/ 66 | .venv2/ 67 | .venv3/ 68 | 69 | # temporary files # 70 | ################### 71 | *.*swp 72 | nohup.out 73 | *.tmp 74 | 75 | # Virtual machines # 76 | #################### 77 | .vagrant/ 78 | 79 | # Pythonics # 80 | ############# 81 | *.py[cod] 82 | 83 | # Packages 84 | *.egg 85 | *.egg-info 86 | dist 87 | build 88 | eggs 89 | parts 90 | var 91 | sdist 92 | develop-eggs 93 | .installed.cfg 94 | lib 95 | lib64 96 | 97 | # Translations 98 | *.mo 99 | 100 | # Mr Developer 101 | .mr.developer.cfg 102 | .project 103 | .pydevproject 104 | 105 | # Complexity 106 | output/*.html 107 | output/*/index.html 108 | 109 | # Sphinx 110 | docs/_build 111 | .scratch.md 112 | ./badrobot 113 | 114 | conf/.config/keybase/ 115 | .DS_Store 116 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/bin/bats"] 2 | path = test/bin/bats 3 | url = https://github.com/bats-core/bats-core 4 | [submodule "test/bin/bats-assert"] 5 | path = test/bin/bats-assert 6 | url = https://github.com/bats-core/bats-assert 7 | [submodule "test/bin/bats-support"] 8 | path = test/bin/bats-support 9 | url = https://github.com/bats-core/bats-support 10 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - main: "." 7 | binary: badrobot 8 | goos: 9 | - darwin 10 | - linux 11 | goarch: 12 | - amd64 13 | - arm64 14 | ignore: 15 | - goos: darwin 16 | goarch: arm 17 | env: 18 | - CGO_ENABLED=0 19 | 20 | checksum: 21 | name_template: "{{ .ProjectName }}_checksums.txt" 22 | 23 | archives: 24 | - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 25 | 26 | snapshot: 27 | name_template: "{{ .Tag }}-next" 28 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior 58 | may be reported by contacting Andrew Martin andy(at)control-plane.io. 59 | All complaints will be reviewed and investigated and will result in a response that is deemed 60 | necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of 62 | an incident. Further details of specific enforcement policies may be 63 | posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.3-alpine AS builder 2 | 3 | RUN echo "badrobot:x:25000:25000:badrobot:/home/badrobot:/sbin/nologin" > /passwd && \ 4 | echo "badrobot:x:25000:" > /group 5 | WORKDIR /badrobot 6 | RUN apk add --no-cache ca-certificates 7 | COPY . . 8 | RUN CGO_ENABLED=0 GOOS=linux go build \ 9 | -ldflags="-s -w -X github.com/controlplaneio/badrobot/cmd.version=$VERSION -X github.com/controlplaneio/badrobot/cmd.commit=$COMMIT" \ 10 | -o badrobot . 11 | 12 | # === 13 | 14 | FROM scratch 15 | WORKDIR /home/badrobot 16 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 17 | COPY --from=builder /passwd /group /etc/ 18 | COPY --from=builder /badrobot/badrobot /bin/badrobot 19 | USER badrobot 20 | 21 | ENTRYPOINT ["/bin/badrobot"] 22 | -------------------------------------------------------------------------------- /Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.3-alpine AS builder 2 | ARG VERSION=unknown 3 | ARG COMMIT=unknown 4 | 5 | WORKDIR /badrobot 6 | 7 | COPY . . 8 | 9 | RUN CGO_ENABLED=0 GOOS=linux go build \ 10 | -ldflags="-s -w -X github.com/controlplaneio/badrobot/cmd.version=$VERSION -X github.com/controlplaneio/badrobot/cmd.commit=$COMMIT" \ 11 | -o badrobot . 12 | 13 | # === 14 | 15 | FROM alpine:3.21.3 16 | 17 | RUN addgroup -S badrobot \ 18 | && adduser -S -g badrobot badrobot \ 19 | && apk --no-cache add ca-certificates 20 | 21 | WORKDIR /home/badrobot 22 | 23 | COPY --from=builder /badrobot/badrobot /bin/badrobot 24 | 25 | USER badrobot 26 | 27 | ENTRYPOINT ["/bin/badrobot"] 28 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Kevin Ward, ControlPlane 2 | Henry Mortimer, ControlPlane 3 | Jack Kelly, ControlPlane 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := badrobot 2 | GITHUB_ORG := controlplaneio 3 | DOCKER_HUB_ORG := controlplane 4 | 5 | ### github.com/controlplaneio/ensure-content.git makefile-header START ### 6 | ifeq ($(NAME),) 7 | $(error NAME required, please add "NAME := project-name" to top of Makefile) 8 | else ifeq ($(GITHUB_ORG),) 9 | $(error GITHUB_ORG required, please add "GITHUB_ORG := controlplaneio" to top of Makefile) 10 | else ifeq ($(DOCKER_HUB_ORG),) 11 | $(error DOCKER_HUB_ORG required, please add "DOCKER_HUB_ORG := controlplane" to top of Makefile) 12 | endif 13 | 14 | PKG := github.com/$(GITHUB_ORG)/$(NAME) 15 | CONTAINER_REGISTRY_FQDN ?= docker.io 16 | CONTAINER_REGISTRY_URL := $(CONTAINER_REGISTRY_FQDN)/$(DOCKER_HUB_ORG)/$(NAME) 17 | 18 | SHELL := /bin/bash 19 | BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ) 20 | 21 | GIT_MESSAGE := $(shell git -c log.showSignature=false \ 22 | log --max-count=1 --pretty=format:"%H" 2>/dev/null) 23 | GIT_SHA := $(shell git -c log.showSignature=false rev-parse HEAD 2>/dev/null) 24 | GIT_TAG := $(shell bash -c 'TAG=$$(git -c log.showSignature=false \ 25 | describe --tags --exact-match --abbrev=0 $(GIT_SHA) 2>/dev/null); echo "$${TAG:-dev}"') 26 | GIT_UNTRACKED_CHANGES := $(shell git -c log.showSignature=false \ 27 | status --porcelain 2>/dev/null) 28 | 29 | ifneq ($(GIT_UNTRACKED_CHANGES),) 30 | GIT_COMMIT := $(GIT_COMMIT)-dirty 31 | ifneq ($(GIT_TAG),dev) 32 | GIT_TAG := $(GIT_TAG)-dirty 33 | endif 34 | endif 35 | 36 | CONTAINER_TAG ?= $(GIT_TAG) 37 | CONTAINER_TAG_LATEST := $(CONTAINER_TAG) 38 | CONTAINER_NAME := $(CONTAINER_REGISTRY_URL):$(CONTAINER_TAG) 39 | 40 | # if no untracked changes and tag is not dev, release `latest` tag 41 | ifeq ($(GIT_UNTRACKED_CHANGES),) 42 | ifneq ($(GIT_TAG),dev) 43 | CONTAINER_TAG_LATEST = latest 44 | endif 45 | endif 46 | 47 | CONTAINER_NAME_LATEST := $(CONTAINER_REGISTRY_URL):$(CONTAINER_TAG_LATEST) 48 | 49 | # golang buildtime, more at https://github.com/jessfraz/pepper/blob/master/Makefile 50 | CTIMEVAR=-X $(PKG)/version.GITCOMMIT=$(GITCOMMIT) -X $(PKG)/version.VERSION=$(VERSION) 51 | GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)" 52 | GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static" 53 | 54 | export NAME CONTAINER_REGISTRY_URL BUILD_DATE GIT_MESSAGE GIT_SHA GIT_TAG \ 55 | CONTAINER_TAG CONTAINER_NAME CONTAINER_NAME_LATEST CONTAINER_NAME_TESTING 56 | ### github.com/controlplaneio/ensure-content.git makefile-header END ### 57 | 58 | LDFLAGS=-s -w \ 59 | -X github.com/controlplaneio/badrobot/cmd.version=$(GIT_TAG)\ 60 | -X github.com/controlplaneio/badrobot/cmd.commit=$(GIT_SHA) 61 | 62 | PACKAGE = none 63 | BATS_PARALLEL_JOBS := $(shell command -v parallel 2>&1 >/dev/null && echo '--jobs 20') 64 | 65 | .PHONY: all 66 | all: help 67 | 68 | # --- 69 | 70 | .PHONY: all 71 | lint: 72 | @echo "+ $@" 73 | make lint-go-fmt 74 | 75 | .PHONY: lint-go-fmt 76 | lint-go-fmt: ## golang fmt check 77 | @echo "+ $@" 78 | gofmt -l -s ./pkg | grep ".*\.go"; if [ "$$?" = "0" ]; then exit 1; fi 79 | 80 | # --- 81 | .PHONY: test 82 | test: ## unit and local acceptance tests 83 | @echo "+ $@" 84 | make test-unit build test-acceptance 85 | 86 | .PHONY: check-and-reinit-submodules 87 | check-and-reinit-submodules: 88 | @if git submodule status | grep "^[-+]" ; then \ 89 | git submodule update --init; \ 90 | fi 91 | 92 | .PHONY: uninit-submodules 93 | uninit-submodules: 94 | git submodule deinit -f . 95 | 96 | .PHONY: test-acceptance 97 | test-acceptance: check-and-reinit-submodules build ## acceptance tests 98 | @echo "+ $@" 99 | bash -xc 'cd test && ./bin/bats/bin/bats $(BATS_PARALLEL_JOBS) .' 100 | 101 | .PHONY: test-unit 102 | test-unit: ## golang unit tests 103 | @echo "+ $@" 104 | CGO_ENABLED=1 go test -race $$(go list ./... | grep -v '/vendor/') -run "$${RUN:-.*}" 105 | 106 | .PHONY: test-unit-verbose 107 | test-unit-verbose: ## golang unit tests (verbose) 108 | @echo "+ $@" 109 | CGO_ENABLED=1 go test -race -v $$(go list ./... | grep -v '/vendor/') -run "$${RUN:-.*}" 110 | 111 | # --- 112 | 113 | .PHONY: build 114 | build: ## golang build 115 | @echo "+ $@" 116 | go build -ldflags "$(LDFLAGS)" -o ./dist/badrobot . 117 | 118 | .PHONY: prune 119 | prune: ## golang dependency prune 120 | @echo "+ $@" 121 | go mod tidy 122 | 123 | # --- 124 | 125 | .PHONY: help 126 | help: ## parse jobs and descriptions from this Makefile 127 | @grep -E '^[ a-zA-Z0-9_-]+:([^=]|$$)' $(MAKEFILE_LIST) \ 128 | | grep -Ev '^help\b[[:space:]]*:' \ 129 | | sort \ 130 | | awk 'BEGIN {FS = ":.*?##"}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 131 | 132 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Our security address 2 | 3 | Contact: security@control-plane.io 4 | Encryption: https://keybase.io/sublimino/pgp_keys.asc 5 | Disclosure: Full 6 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | "go.uber.org/zap" 11 | "go.uber.org/zap/zapcore" 12 | ) 13 | 14 | var logger *zap.SugaredLogger 15 | 16 | var rootCmd = &cobra.Command{ 17 | Use: "badrobot", 18 | Short: "badrobot command line", 19 | Long: ` 20 | Validate Kubernetes Operator resources for security risks`, 21 | } 22 | 23 | // Execute runs badrobot 24 | func Execute() { 25 | var err error 26 | 27 | // logger writes to stderr 28 | logger, err = NewLogger("info", "console") 29 | if err != nil { 30 | log.Fatalf("can't initialize zap logger: %v", err) 31 | } 32 | 33 | defer logger.Sync() 34 | 35 | rootCmd.SetArgs(os.Args[1:]) 36 | if err := rootCmd.Execute(); err != nil { 37 | e := err.Error() 38 | 39 | fmt.Println(strings.ToUpper(e[:1]) + e[1:]) 40 | os.Exit(1) 41 | } 42 | } 43 | 44 | // NewLogger creates a logger 45 | func NewLogger(logLevel string, zapEncoding string) (*zap.SugaredLogger, error) { 46 | level := zap.NewAtomicLevelAt(zapcore.InfoLevel) 47 | switch logLevel { 48 | case "debug": 49 | level = zap.NewAtomicLevelAt(zapcore.DebugLevel) 50 | case "info": 51 | level = zap.NewAtomicLevelAt(zapcore.InfoLevel) 52 | case "warn": 53 | level = zap.NewAtomicLevelAt(zapcore.WarnLevel) 54 | case "error": 55 | level = zap.NewAtomicLevelAt(zapcore.ErrorLevel) 56 | case "fatal": 57 | level = zap.NewAtomicLevelAt(zapcore.FatalLevel) 58 | case "panic": 59 | level = zap.NewAtomicLevelAt(zapcore.PanicLevel) 60 | } 61 | 62 | zapEncoderConfig := zapcore.EncoderConfig{ 63 | TimeKey: "timestamp", 64 | LevelKey: "severity", 65 | NameKey: "logger", 66 | CallerKey: "caller", 67 | MessageKey: "message", 68 | StacktraceKey: "stacktrace", 69 | LineEnding: zapcore.DefaultLineEnding, 70 | EncodeLevel: zapcore.LowercaseLevelEncoder, 71 | EncodeTime: zapcore.ISO8601TimeEncoder, 72 | EncodeDuration: zapcore.SecondsDurationEncoder, 73 | EncodeCaller: zapcore.ShortCallerEncoder, 74 | } 75 | 76 | zapConfig := zap.Config{ 77 | Level: level, 78 | Development: false, 79 | Sampling: &zap.SamplingConfig{ 80 | Initial: 100, 81 | Thereafter: 100, 82 | }, 83 | Encoding: zapEncoding, 84 | EncoderConfig: zapEncoderConfig, 85 | OutputPaths: []string{"stderr"}, 86 | ErrorOutputPaths: []string{"stderr"}, 87 | } 88 | 89 | logger, err := zapConfig.Build() 90 | if err != nil { 91 | return nil, err 92 | } 93 | return logger.Sugar(), nil 94 | } 95 | -------------------------------------------------------------------------------- /cmd/scan.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/controlplaneio/badrobot/pkg/report" 12 | "github.com/controlplaneio/badrobot/pkg/ruler" 13 | "github.com/spf13/cobra" 14 | "go.uber.org/zap" 15 | ) 16 | 17 | type ScanFailedValidationError struct { 18 | } 19 | 20 | func (e *ScanFailedValidationError) Error() string { 21 | return "BadRobot scan failed" 22 | } 23 | 24 | var debug bool 25 | var absolutePath bool 26 | var format string 27 | var template string 28 | var schemaDir string 29 | var outputLocation string 30 | var exitCode int 31 | 32 | func init() { 33 | scanCmd.Flags().BoolVar(&debug, "debug", false, "turn on debug logs") 34 | scanCmd.Flags().BoolVar(&absolutePath, "absolute-path", false, "use the absolute path for the file name") 35 | scanCmd.Flags().StringVarP(&format, "format", "f", "json", "Set output format (json, template)") 36 | scanCmd.Flags().StringVar(&schemaDir, "schema-dir", "", "Sets the directory for the json schemas") 37 | scanCmd.Flags().StringVarP(&template, "template", "t", "", "Set output template, it will check for a file or read input as the") 38 | scanCmd.Flags().StringVarP(&outputLocation, "output", "o", "", "Set output location") 39 | scanCmd.Flags().IntVar(&exitCode, "exit-code", 2, "Set the exit-code to use on failure") 40 | rootCmd.AddCommand(scanCmd) 41 | } 42 | 43 | // File holds the name and contents 44 | type File struct { 45 | fileName string 46 | fileBytes []byte 47 | } 48 | 49 | func getInput(args []string) (File, error) { 50 | var file File 51 | 52 | if len(args) == 1 && (args[0] == "-" || args[0] == "/dev/stdin") { 53 | fileBytes, err := ioutil.ReadAll(os.Stdin) 54 | if err != nil { 55 | return file, err 56 | } 57 | file = File{ 58 | fileName: "STDIN", 59 | fileBytes: fileBytes, 60 | } 61 | return file, nil 62 | } 63 | fileName := args[0] 64 | filePath, err := filepath.Abs(fileName) 65 | if err != nil { 66 | return file, err 67 | } 68 | if absolutePath { 69 | fileName = filePath 70 | } 71 | 72 | fileBytes, err := ioutil.ReadFile(filePath) 73 | if err != nil { 74 | return file, err 75 | } 76 | file = File{ 77 | fileName: fileName, 78 | fileBytes: fileBytes, 79 | } 80 | return file, nil 81 | } 82 | 83 | var scanCmd = &cobra.Command{ 84 | Use: `scan [file]`, 85 | Short: "Scans Kubernetes Operator resource YAML or JSON", 86 | Example: ` badrobot scan ./operator.yaml`, 87 | RunE: func(cmd *cobra.Command, args []string) error { 88 | if len(args) < 1 { 89 | return fmt.Errorf("file path is required") 90 | } 91 | 92 | if debug { 93 | z, err := zap.NewDevelopment() 94 | if err != nil { 95 | log.Fatalf("can't initialize zap logger: %v", err) 96 | } 97 | logger = z.Sugar() 98 | } 99 | 100 | rootCmd.SilenceErrors = true 101 | rootCmd.SilenceUsage = true 102 | 103 | file, err := getInput(args) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | reports, err := ruler.NewRuleset(logger).Run(file.fileName, file.fileBytes, schemaDir) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | if len(reports) == 0 { 114 | return fmt.Errorf("invalid input %s", file.fileName) 115 | } 116 | 117 | var lowScore bool 118 | for _, r := range reports { 119 | if r.Score <= 0 { 120 | lowScore = true 121 | break 122 | } 123 | } 124 | 125 | var buff bytes.Buffer 126 | err = report.WriteReports(format, &buff, reports, template) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | if outputLocation != "" { 132 | err = ioutil.WriteFile(outputLocation, buff.Bytes(), 0644) 133 | if err != nil { 134 | logger.Debugf("Couldn't write output to %s", outputLocation) 135 | } 136 | } 137 | 138 | out := buff.String() 139 | fmt.Println(out) 140 | 141 | if len(reports) > 0 && !lowScore { 142 | return nil 143 | } 144 | 145 | os.Exit(exitCode) 146 | return &ScanFailedValidationError{} 147 | }, 148 | } 149 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | // vars injected with ldflags at build time (this can be done automatically by goreleaser) 11 | version = "unknown" 12 | commit = "unknown" 13 | ) 14 | 15 | func init() { 16 | rootCmd.AddCommand(versionCmd) 17 | } 18 | 19 | var versionCmd = &cobra.Command{ 20 | Use: `version`, 21 | Short: "Prints badrobot version", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | fmt.Printf("version %s\ngit commit %s\n", version, commit) 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /doc/images/badrobot_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/controlplaneio/badrobot/21d0ef4861bcb72aca4e51f45b5853dfa4f16205/doc/images/badrobot_logo.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/controlplaneio/badrobot 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/ghodss/yaml v1.0.0 9 | github.com/spf13/cobra v1.9.1 10 | github.com/thedevsaddam/gojsonq/v2 v2.5.2 11 | go.uber.org/zap v1.27.0 12 | k8s.io/api v0.33.1 13 | ) 14 | 15 | require ( 16 | github.com/fxamacker/cbor/v2 v2.8.0 // indirect 17 | github.com/go-logr/logr v1.4.2 // indirect 18 | github.com/gogo/protobuf v1.3.2 // indirect 19 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 20 | github.com/json-iterator/go v1.1.12 // indirect 21 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 22 | github.com/modern-go/reflect2 v1.0.2 // indirect 23 | github.com/spf13/pflag v1.0.6 // indirect 24 | github.com/x448/float16 v0.8.4 // indirect 25 | go.uber.org/multierr v1.11.0 // indirect 26 | golang.org/x/net v0.40.0 // indirect 27 | golang.org/x/text v0.25.0 // indirect 28 | gopkg.in/inf.v0 v0.9.1 // indirect 29 | gopkg.in/yaml.v2 v2.4.0 // indirect 30 | k8s.io/apimachinery v0.33.1 // indirect 31 | k8s.io/klog/v2 v2.130.1 // indirect 32 | k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect 33 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 34 | sigs.k8s.io/randfill v1.0.0 // indirect 35 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect 36 | sigs.k8s.io/yaml v1.4.0 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/controlplaneio/badrobot/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/report/writer.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "html" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "strings" 13 | "text/template" 14 | "time" 15 | 16 | "github.com/controlplaneio/badrobot/pkg/ruler" 17 | ) 18 | 19 | // Heavily based on aquasecurity/trivy's reporter 20 | 21 | // Now returns the current time 22 | var Now = time.Now 23 | 24 | type reports ruler.Reports 25 | 26 | // WriteReports writes the result to output, format as passed in argument 27 | func WriteReports(format string, output io.Writer, reports reports, outputTemplate string) error { 28 | var writer Writer 29 | switch format { 30 | case "json": 31 | writer = &JSONWriter{Output: output} 32 | case "template": 33 | var err error 34 | if len(outputTemplate) == 0 { 35 | return errors.New("template is unset, please specify with --template") 36 | } 37 | if writer, err = NewTemplateWriter(output, outputTemplate); err != nil { 38 | return err 39 | } 40 | default: 41 | return errors.New("Unrecognized format specified") 42 | } 43 | 44 | if err := writer.Write(reports); err != nil { 45 | return err 46 | } 47 | return nil 48 | } 49 | 50 | // Writer defines the result write operation 51 | type Writer interface { 52 | Write(reports) error 53 | } 54 | 55 | // JSONWriter implements result Writer 56 | type JSONWriter struct { 57 | Output io.Writer 58 | } 59 | 60 | // PrettyJSON will indent JSON to be pretty 61 | func PrettyJSON(jsonBytes []byte) ([]byte, error) { 62 | var out bytes.Buffer 63 | err := json.Indent(&out, jsonBytes, "", " ") 64 | if err != nil { 65 | return jsonBytes, err 66 | } 67 | return out.Bytes(), nil 68 | } 69 | 70 | // Write writes the reports in JSON format 71 | func (jw JSONWriter) Write(reports reports) error { 72 | output, err := json.Marshal(reports) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | formattedOutput, err := PrettyJSON(output) 78 | if err != nil { 79 | return err 80 | } 81 | if _, err = fmt.Fprint(jw.Output, string(formattedOutput)); err != nil { 82 | return err 83 | } 84 | return nil 85 | } 86 | 87 | // TemplateWriter write result in custom format defined by user's template 88 | type TemplateWriter struct { 89 | Output io.Writer 90 | Template *template.Template 91 | } 92 | 93 | // NewTemplateWriter is the factory method to return TemplateWriter object 94 | func NewTemplateWriter(output io.Writer, outputTemplate string) (*TemplateWriter, error) { 95 | // if outputTemplate is a file read it and use that 96 | if _, err := os.Stat(outputTemplate); err == nil { 97 | buf, err := ioutil.ReadFile(outputTemplate) 98 | if err != nil { 99 | return nil, err 100 | } 101 | outputTemplate = string(buf) 102 | } 103 | 104 | tmpl, err := template.New("output template").Funcs(template.FuncMap{ 105 | "endWithPeriod": func(input string) string { 106 | if !strings.HasSuffix(input, ".") { 107 | input += "." 108 | } 109 | return input 110 | }, 111 | "toLower": func(input string) string { 112 | return strings.ToLower(input) 113 | }, 114 | "escapeString": func(input string) string { 115 | return html.EscapeString(input) 116 | }, 117 | "getCurrentTime": func() string { 118 | return Now().UTC().Format(time.RFC3339Nano) 119 | }, 120 | "joinSlices": func(slices ...[]ruler.RuleRef) []ruler.RuleRef { 121 | var resultSlice []ruler.RuleRef 122 | for _, slice := range slices { 123 | resultSlice = append(resultSlice, slice...) 124 | } 125 | return resultSlice 126 | }, 127 | }).Parse(outputTemplate) 128 | if err != nil { 129 | return nil, err 130 | } 131 | return &TemplateWriter{Output: output, Template: tmpl}, nil 132 | } 133 | 134 | // Write writes result 135 | func (tw TemplateWriter) Write(reports reports) error { 136 | err := tw.Template.Execute(tw.Output, reports) 137 | if err != nil { 138 | return err 139 | } 140 | return nil 141 | } 142 | -------------------------------------------------------------------------------- /pkg/ruler/report.go: -------------------------------------------------------------------------------- 1 | package ruler 2 | 3 | type Reports []Report 4 | 5 | type Report struct { 6 | Object string `json:"object"` 7 | Valid bool `json:"valid"` 8 | FileName string `json:"fileName"` 9 | Rules []RuleRef `json:"-"` 10 | Message string `json:"message,omitempty"` 11 | Score int `json:"score"` 12 | Scoring RuleScoring `json:"scoring,omitempty"` 13 | } 14 | 15 | type RuleScoring struct { 16 | Critical []RuleRef `json:"critical,omitempty"` 17 | Passed []RuleRef `json:"passed,omitempty"` 18 | Advise []RuleRef `json:"advise,omitempty"` 19 | } 20 | 21 | type RuleRef struct { 22 | ID string `json:"id"` 23 | Selector string `json:"selector"` 24 | Reason string `json:"reason"` 25 | Weight int `json:"weight,omitempty"` 26 | Link string `json:"href,omitempty"` 27 | Containers int `json:"-"` 28 | Points int `json:"points"` 29 | } 30 | 31 | // This implements a custom sort interface (Len, Swap, Less) for the report listing. 32 | // Each scan can produce a different ordering of the reported tests. To have a single 33 | // deterministic report response for the same input requires sort to never draw. 34 | // Assumption below is: the combination of points, then selector text, should be unique 35 | // This is applied to the output of scan for each of the Critical and Advisory lists. 36 | 37 | type RuleRefCustomOrder []RuleRef 38 | 39 | func (rr RuleRefCustomOrder) Len() int { return len(rr) } 40 | 41 | func (rr RuleRefCustomOrder) Swap(i, j int) { rr[i], rr[j] = rr[j], rr[i] } 42 | 43 | func (rr RuleRefCustomOrder) Less(i, j int) bool { 44 | if rr[i].Points != rr[j].Points { 45 | // no integer absolute fn in golang 46 | if rr[i].Points > 0 || rr[j].Points > 0 { 47 | return rr[i].Points > rr[j].Points 48 | } 49 | return rr[i].Points < rr[j].Points 50 | } 51 | return rr[i].Selector < rr[j].Selector 52 | } 53 | -------------------------------------------------------------------------------- /pkg/ruler/rule.go: -------------------------------------------------------------------------------- 1 | package ruler 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/thedevsaddam/gojsonq/v2" 7 | ) 8 | 9 | type NotSupportedError struct { 10 | Kind string 11 | } 12 | 13 | func (e *NotSupportedError) Error() string { 14 | return fmt.Sprintf("rule does not apply to kind %s", e.Kind) 15 | } 16 | 17 | type Rule struct { 18 | Selector string 19 | ID string 20 | Title string 21 | Reason string 22 | Link string 23 | Kinds []string 24 | Points int 25 | Weight int 26 | Advise int 27 | Predicate func([]byte) int 28 | } 29 | 30 | // Eval executes the predicate if the kind matches the rule 31 | func (r *Rule) Eval(json []byte) (int, error) { 32 | jq := gojsonq.New().Reader(bytes.NewReader(json)).From("kind") 33 | if jq.Error() != nil { 34 | return 0, jq.Error() 35 | } 36 | 37 | kind := fmt.Sprintf("%s", jq.Get()) 38 | 39 | var match bool 40 | for _, k := range r.Kinds { 41 | if k == kind { 42 | match = true 43 | break 44 | } 45 | } 46 | 47 | if match { 48 | count := r.Predicate(json) 49 | return count, nil 50 | } else { 51 | return 0, &NotSupportedError{Kind: kind} 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/ruler/rule_test.go: -------------------------------------------------------------------------------- 1 | package ruler 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/controlplaneio/badrobot/pkg/rules" 8 | "github.com/ghodss/yaml" 9 | ) 10 | 11 | func TestRule_Eval(t *testing.T) { 12 | var data = ` 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: controller-manager 18 | namespace: system 19 | labels: 20 | control-plane: controller-manager 21 | spec: 22 | selector: 23 | matchLabels: 24 | control-plane: controller-manager 25 | replicas: 1 26 | template: 27 | metadata: 28 | annotations: 29 | kubectl.kubernetes.io/default-container: manager 30 | labels: 31 | control-plane: controller-manager 32 | spec: 33 | containers: 34 | - command: 35 | - /manager 36 | args: 37 | - --leader-elect 38 | image: controller:latest 39 | name: manager 40 | securityContext: 41 | allowPrivilegeEscalation: false 42 | ` 43 | 44 | json, err := yaml.YAMLToJSON([]byte(data)) 45 | if err != nil { 46 | t.Fatal(err.Error()) 47 | } 48 | 49 | rule := &Rule{ 50 | Predicate: rules.AllowPrivilegeEscalation, 51 | Kinds: []string{"Deployment"}, 52 | } 53 | 54 | matchedSecurityContext, err := rule.Eval(json) 55 | if err != nil { 56 | t.Fatal(err.Error()) 57 | } 58 | if matchedSecurityContext != 0 { 59 | fmt.Printf("%v", matchedSecurityContext) 60 | t.Errorf("%s", fmt.Sprintf("Rule failed when it shouldn't with count %d", matchedSecurityContext)) 61 | } 62 | } 63 | 64 | func TestRule_EvalDoesNotApply(t *testing.T) { 65 | var data = ` 66 | --- 67 | apiVersion: apps/v1 68 | kind: StatefulSet 69 | spec: 70 | template: 71 | spec: 72 | containers: 73 | - name: manager 74 | image: controller:latest 75 | securityContext: 76 | allowPrivilegeEscalation: true 77 | ` 78 | 79 | json, err := yaml.YAMLToJSON([]byte(data)) 80 | if err != nil { 81 | t.Fatal(err.Error()) 82 | } 83 | 84 | rule := &Rule{ 85 | Predicate: rules.AllowPrivilegeEscalation, 86 | Kinds: []string{"Deployment"}, 87 | } 88 | 89 | _, err = rule.Eval(json) 90 | if err == nil { 91 | t.Errorf("Rule succeeded when it shouldn't") 92 | } 93 | } 94 | 95 | func TestRule_EvalNoKind(t *testing.T) { 96 | var data = ` 97 | --- 98 | apiVersion: apps/v1 99 | spec: 100 | template: 101 | spec: 102 | containers: 103 | - name: manager 104 | image: controller:latest 105 | securityContext: 106 | allowPrivilegeEscalation: true 107 | ` 108 | 109 | json, err := yaml.YAMLToJSON([]byte(data)) 110 | if err != nil { 111 | t.Fatal(err.Error()) 112 | } 113 | 114 | rule := &Rule{ 115 | Predicate: rules.AllowPrivilegeEscalation, 116 | Kinds: []string{"Deployment"}, 117 | } 118 | 119 | _, err = rule.Eval(json) 120 | if err == nil { 121 | t.Errorf("Rule succeeded when it shouldn't") 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /pkg/ruler/ruleset_test.go: -------------------------------------------------------------------------------- 1 | package ruler 2 | 3 | import ( 4 | // "strings" 5 | "testing" 6 | 7 | "github.com/ghodss/yaml" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | const schemaDir = "" 12 | 13 | func TestRuleset_Run(t *testing.T) { 14 | var data = ` 15 | --- 16 | apiVersion: v1 17 | kind: Namespace 18 | metadata: 19 | name: kube-system 20 | ` 21 | 22 | json, err := yaml.YAMLToJSON([]byte(data)) 23 | if err != nil { 24 | t.Fatal(err.Error()) 25 | } 26 | 27 | report := NewRuleset(zap.NewNop().Sugar()).generateReport("operator.yaml", json, schemaDir) 28 | 29 | critical := len(report.Scoring.Critical) 30 | if critical < 1 { 31 | t.Errorf("Got %v critical rules wanted many", critical) 32 | } 33 | 34 | if report.Score > 0 { 35 | t.Errorf("Got score %v wanted a negative value", report.Score) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/rules/admissionControllerClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R22-RBAC - ClusterRole has full permissions over admission controllers 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func AdmissionControllerClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("admissionregistration.k8s.io", rule.APIGroups) && 21 | containsAny([]string{"mutatingwebhookconfigurations", "validatingwebhookconfigurations"}, rule.Resources) && 22 | containsAny([]string{"*", "create", "patch", "update", "delete", "deletecollection"}, rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | 29 | } 30 | -------------------------------------------------------------------------------- /pkg/rules/admissionControllerClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Adm_Ctr_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - admissionregistration.k8s.io 19 | resources: 20 | - mutatingwebhookconfigurations 21 | - validatingwebhookconfigurations 22 | verbs: 23 | - "*" 24 | ` 25 | 26 | json, err := yaml.YAMLToJSON([]byte(data)) 27 | if err != nil { 28 | t.Fatal(err.Error()) 29 | } 30 | 31 | rbac := AdmissionControllerClusterRole(json) 32 | if rbac != 1 { 33 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 34 | } 35 | } 36 | 37 | func Test_Adm_Ctr_Multiple_API_Group(t *testing.T) { 38 | var data = ` 39 | --- 40 | apiVersion: rbac.authorization.k8s.io/v1 41 | kind: ClusterRole 42 | metadata: 43 | name: example-operator 44 | rules: 45 | - apiGroups: 46 | - apps 47 | - admissionregistration.k8s.io 48 | resources: 49 | - mutatingwebhookconfigurations 50 | - validatingwebhookconfigurations 51 | - deployment 52 | verbs: 53 | - "*" 54 | ` 55 | 56 | json, err := yaml.YAMLToJSON([]byte(data)) 57 | if err != nil { 58 | t.Fatal(err.Error()) 59 | } 60 | 61 | rbac := AdmissionControllerClusterRole(json) 62 | if rbac != 1 { 63 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 64 | } 65 | } 66 | 67 | func Test_Adm_Ctr_Multiple_Rules(t *testing.T) { 68 | var data = ` 69 | --- 70 | apiVersion: rbac.authorization.k8s.io/v1 71 | kind: ClusterRole 72 | metadata: 73 | name: example-operator 74 | rules: 75 | - apiGroups: 76 | - apps 77 | resources: 78 | - deployment 79 | verbs: 80 | - "*" 81 | - apiGroups: 82 | - admissionregistration.k8s.io 83 | resources: 84 | - mutatingwebhookconfigurations 85 | - validatingwebhookconfigurations 86 | verbs: 87 | - "*" 88 | ` 89 | 90 | json, err := yaml.YAMLToJSON([]byte(data)) 91 | if err != nil { 92 | t.Fatal(err.Error()) 93 | } 94 | 95 | rbac := AdmissionControllerClusterRole(json) 96 | if rbac != 1 { 97 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 98 | } 99 | } 100 | 101 | func Test_Incorrect_Adm_Ctr_Permissions(t *testing.T) { 102 | var data = ` 103 | --- 104 | apiVersion: rbac.authorization.k8s.io/v1 105 | kind: ClusterRole 106 | metadata: 107 | name: example-operator 108 | rules: 109 | - apiGroups: 110 | - "" 111 | resources: 112 | - mutatingwebhookconfigurations 113 | - validatingwebhookconfigurations 114 | verbs: 115 | - "*" 116 | ` 117 | 118 | json, err := yaml.YAMLToJSON([]byte(data)) 119 | if err != nil { 120 | t.Fatal(err.Error()) 121 | } 122 | 123 | rbac := AdmissionControllerClusterRole(json) 124 | if rbac != 0 { 125 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 126 | } 127 | } 128 | 129 | func Test_Adm_Ctr_Verbs_Permissions(t *testing.T) { 130 | var data = ` 131 | --- 132 | apiVersion: rbac.authorization.k8s.io/v1 133 | kind: ClusterRole 134 | metadata: 135 | name: example-operator 136 | rules: 137 | - apiGroups: 138 | - admissionregistration.k8s.io 139 | resources: 140 | - mutatingwebhookconfigurations 141 | - validatingwebhookconfigurations 142 | verbs: 143 | - create 144 | - update 145 | - delete 146 | - patch 147 | - deletecollection 148 | ` 149 | json, err := yaml.YAMLToJSON([]byte(data)) 150 | if err != nil { 151 | t.Fatal(err.Error()) 152 | } 153 | 154 | rbac := AdmissionControllerClusterRole(json) 155 | if rbac != 1 { 156 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 157 | } 158 | } 159 | 160 | func Test_Adm_Ctr_Mutliple_Rules(t *testing.T) { 161 | var data = ` 162 | --- 163 | apiVersion: rbac.authorization.k8s.io/v1 164 | kind: ClusterRole 165 | metadata: 166 | name: example-operator 167 | rules: 168 | - apiGroups: 169 | - admissionregistration.k8s.io 170 | resources: 171 | - mutatingwebhookconfigurations 172 | - validatingwebhookconfigurations 173 | verbs: 174 | - create 175 | - update 176 | - delete 177 | - patch 178 | - deletecollection 179 | - apiGroups: 180 | - "" 181 | resources: 182 | - pods 183 | verbs: 184 | - create 185 | - update 186 | - delete 187 | - patch 188 | - deletecollection 189 | ` 190 | json, err := yaml.YAMLToJSON([]byte(data)) 191 | if err != nil { 192 | t.Fatal(err.Error()) 193 | } 194 | 195 | rbac := AdmissionControllerClusterRole(json) 196 | if rbac != 1 { 197 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /pkg/rules/allowPrivilegeEscalation.go: -------------------------------------------------------------------------------- 1 | // OPR-R4-SC - securityContext set to allowPrivilegeEscalation: true 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func AllowPrivilegeEscalation(json []byte) int { 13 | sc := 0 14 | spec := getSpecSelector(json) 15 | 16 | jqContainers := gojsonq.New().Reader(bytes.NewReader(json)). 17 | From(spec+".containers"). 18 | Where("securityContext", "!=", nil). 19 | Where("securityContext.allowPrivilegeEscalation", "!=", nil). 20 | Where("securityContext.allowPrivilegeEscalation", "=", true) 21 | 22 | jqSecurityContext := gojsonq.New().Reader(bytes.NewReader(json)). 23 | From(spec+".securityContext"). 24 | Where("securityContext", "!=", nil). 25 | Where("securityContext.allowPrivilegeEscalation", "!=", nil) 26 | 27 | if strings.Contains(fmt.Sprintf("%v", jqSecurityContext.Get()), "allowPrivilegeEscalation:true") { 28 | sc++ 29 | } 30 | 31 | return jqContainers.Count() + sc 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/allowPrivilegeEscalation_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | func Test_AllowPrivilegeEscalation_Deploy(t *testing.T) { 9 | var data = ` 10 | --- 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | spec: 14 | template: 15 | spec: 16 | containers: 17 | - name: c1 18 | securityContext: 19 | allowPrivilegeEscalation: true 20 | - name: c2 21 | securityContext: 22 | allowPrivilegeEscalation: true 23 | - name: c3 24 | securityContext: 25 | allowPrivilegeEscalation: true 26 | ` 27 | 28 | json, err := yaml.YAMLToJSON([]byte(data)) 29 | if err != nil { 30 | t.Fatal(err.Error()) 31 | } 32 | 33 | securityContext := AllowPrivilegeEscalation(json) 34 | if securityContext != 3 { 35 | t.Errorf("Got %v securityContext wanted %v", securityContext, 3) 36 | } 37 | } 38 | 39 | func Test_AllowPrivilegeEscalation_Spec(t *testing.T) { 40 | var data = ` 41 | apiVersion: apps/v1 42 | kind: Deployment 43 | metadata: 44 | name: controller-manager 45 | namespace: system 46 | labels: 47 | control-plane: controller-manager 48 | spec: 49 | selector: 50 | matchLabels: 51 | control-plane: controller-manager 52 | replicas: 1 53 | template: 54 | metadata: 55 | annotations: 56 | kubectl.kubernetes.io/default-container: manager 57 | labels: 58 | control-plane: controller-manager 59 | spec: 60 | securityContext: 61 | allowPrivilegeEscalation: false 62 | ` 63 | 64 | json, err := yaml.YAMLToJSON([]byte(data)) 65 | if err != nil { 66 | t.Fatal(err.Error()) 67 | } 68 | 69 | securityContext := AllowPrivilegeEscalation(json) 70 | if securityContext != 0 { 71 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/rules/bindClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R17-RBAC - ClusterRole has bind permissions 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func BindClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("rbac.authorization.k8s.io", rule.APIGroups) && 21 | contains("clusterroles", rule.Resources) && 22 | contains("bind", rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rules/bindClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Bind_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - rbac.authorization.k8s.io 19 | resources: 20 | - clusterroles 21 | verbs: 22 | - bind 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := BindClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Incorrect_Bind_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - rbac.authorization.k8s.io 46 | resources: 47 | - serviceaccounts 48 | verbs: 49 | - bind 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := BindClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_Bind_Permissions_Multiple_Rules(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - rbac.authorization.k8s.io 73 | resources: 74 | - clusterroles 75 | verbs: 76 | - bind 77 | - apiGroups: 78 | - apps 79 | resources: 80 | - deployments 81 | verbs: 82 | - "*" 83 | ` 84 | 85 | json, err := yaml.YAMLToJSON([]byte(data)) 86 | if err != nil { 87 | t.Fatal(err.Error()) 88 | } 89 | 90 | rbac := BindClusterRole(json) 91 | if rbac != 1 { 92 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/rules/capSysAdmin.go: -------------------------------------------------------------------------------- 1 | // OPR-R9-SC - securityContext adds CAP_SYS_ADMIN Linux capability 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func CapSysAdmin(json []byte) int { 13 | sc := 0 14 | spec := getSpecSelector(json) 15 | 16 | capAdd := gojsonq.New().Reader(bytes.NewReader(json)). 17 | From(spec + ".containers"). 18 | Only("securityContext.capabilities.add") 19 | 20 | if capAdd != nil && strings.Contains(fmt.Sprintf("%v", capAdd), "SYS_ADMIN") { 21 | sc++ 22 | } 23 | 24 | capAddSpec := gojsonq.New().Reader(bytes.NewReader(json)). 25 | From(spec + ".securityContext.capabilities.add").Get() 26 | 27 | if strings.Contains(fmt.Sprintf("%v", capAddSpec), "SYS_ADMIN") { 28 | sc++ 29 | } 30 | 31 | return sc 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/capSysAdmin_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | func Test_CapSysAdmin_Pod(t *testing.T) { 9 | var data = ` 10 | --- 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | metadata: 14 | name: controller-manager 15 | namespace: system 16 | labels: 17 | control-plane: controller-manager 18 | spec: 19 | selector: 20 | matchLabels: 21 | control-plane: controller-manager 22 | replicas: 1 23 | template: 24 | metadata: 25 | annotations: 26 | kubectl.kubernetes.io/default-container: manager 27 | labels: 28 | control-plane: controller-manager 29 | spec: 30 | containers: 31 | - name: c1 32 | image: controller:latest 33 | securityContext: 34 | capabilities: 35 | add: 36 | - SYS_ADMIN 37 | - name: c2 38 | image: controller:latest 39 | securityContext: 40 | capabilities: 41 | ` 42 | 43 | json, err := yaml.YAMLToJSON([]byte(data)) 44 | if err != nil { 45 | t.Fatal(err.Error()) 46 | } 47 | 48 | securityContext := CapSysAdmin(json) 49 | if securityContext != 1 { 50 | t.Errorf("Got %v securityContext wanted %v", securityContext, 1) 51 | } 52 | } 53 | 54 | func Test_CapSysAdmin_Spec(t *testing.T) { 55 | var data = ` 56 | --- 57 | apiVersion: apps/v1 58 | kind: Deployment 59 | metadata: 60 | name: controller-manager 61 | namespace: system 62 | labels: 63 | control-plane: controller-manager 64 | spec: 65 | selector: 66 | matchLabels: 67 | control-plane: controller-manager 68 | replicas: 1 69 | template: 70 | metadata: 71 | annotations: 72 | kubectl.kubernetes.io/default-container: manager 73 | labels: 74 | control-plane: controller-manager 75 | spec: 76 | securityContext: 77 | capabilities: 78 | add: 79 | - SYS_ADMIN 80 | containers: 81 | - name: c1 82 | securityContext: 83 | capabilities: 84 | add: 85 | - SYS_ADMIN 86 | ` 87 | 88 | json, err := yaml.YAMLToJSON([]byte(data)) 89 | if err != nil { 90 | t.Fatal(err.Error()) 91 | } 92 | 93 | securityContext := CapSysAdmin(json) 94 | if securityContext != 2 { 95 | t.Errorf("Got %v securityContext wanted %v", securityContext, 2) 96 | } 97 | } 98 | 99 | func Test_CapSysAdmin_Missing(t *testing.T) { 100 | var data = ` 101 | --- 102 | apiVersion: v1 103 | kind: Pod 104 | spec: 105 | containers: 106 | - name: c1 107 | ` 108 | 109 | json, err := yaml.YAMLToJSON([]byte(data)) 110 | if err != nil { 111 | t.Fatal(err.Error()) 112 | } 113 | 114 | securityContext := CapSysAdmin(json) 115 | if securityContext != 0 { 116 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /pkg/rules/clusterAdmin.go: -------------------------------------------------------------------------------- 1 | // OPR-R10-RBAC - Runs as Cluster Admin 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "regexp" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func ClusterAdmin(json []byte) int { 13 | rbac := 0 14 | 15 | jqCRB := gojsonq.New().Reader(bytes.NewReader(json)). 16 | From("roleRef.name").Get() 17 | 18 | reCRB := regexp.MustCompile(`^cluster-admin$`) 19 | 20 | if reCRB.MatchString(fmt.Sprintf("%v", jqCRB)) { 21 | rbac++ 22 | } 23 | return rbac 24 | 25 | } 26 | -------------------------------------------------------------------------------- /pkg/rules/clusterAdmin_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Cluster_Admin_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | kind: ClusterRoleBinding 13 | apiVersion: rbac.authorization.k8s.io/v1 14 | metadata: 15 | name: manager-rolebinding 16 | subjects: 17 | - kind: ServiceAccount 18 | name: manager-rolebinding 19 | # Replace this with the namespace the operator is deployed in. 20 | namespace: system 21 | roleRef: 22 | kind: ClusterRole 23 | name: cluster-admin 24 | apiGroup: rbac.authorization.k8s.io 25 | ` 26 | 27 | json, err := yaml.YAMLToJSON([]byte(data)) 28 | if err != nil { 29 | t.Fatal(err.Error()) 30 | } 31 | 32 | rbac := ClusterAdmin(json) 33 | if rbac != 1 { 34 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 35 | } 36 | } 37 | 38 | func Test_Incorrect_Cluster_Admin_Permissions(t *testing.T) { 39 | var data = ` 40 | --- 41 | kind: ClusterRoleBinding 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | metadata: 44 | name: manager-rolebinding 45 | subjects: 46 | - kind: ServiceAccount 47 | name: manager-rolebinding 48 | # Replace this with the namespace the operator is deployed in. 49 | namespace: system 50 | roleRef: 51 | kind: ClusterRole 52 | name: cluster-administrator 53 | apiGroup: rbac.authorization.k8s.io 54 | ` 55 | 56 | json, err := yaml.YAMLToJSON([]byte(data)) 57 | if err != nil { 58 | t.Fatal(err.Error()) 59 | } 60 | 61 | rbac := ClusterAdmin(json) 62 | if rbac != 0 { 63 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/rules/customResourceClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R21-RBAC - ClusterRole has full permissions over any custom resource definitions 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func CustomResourceClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("apiextensions.k8s.io", rule.APIGroups) && 21 | contains("customresourcedefinitions", rule.Resources) && 22 | containsAny([]string{"*", "create", "patch", "update", "delete", "deletecollection"}, rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rules/customResourceClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_CRD_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - apiextensions.k8s.io 19 | resources: 20 | - customresourcedefinitions 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := CustomResourceClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Incorrect_CRD_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - customresourcedefinitions 48 | verbs: 49 | - create 50 | - patch 51 | - modify 52 | - delete 53 | - deletecollection 54 | ` 55 | 56 | json, err := yaml.YAMLToJSON([]byte(data)) 57 | if err != nil { 58 | t.Fatal(err.Error()) 59 | } 60 | 61 | rbac := CustomResourceClusterRole(json) 62 | if rbac != 0 { 63 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 64 | } 65 | } 66 | 67 | func Test_CRD_Verbs_Permissions(t *testing.T) { 68 | var data = ` 69 | --- 70 | apiVersion: rbac.authorization.k8s.io/v1 71 | kind: ClusterRole 72 | metadata: 73 | name: example-operator 74 | rules: 75 | - apiGroups: 76 | - apiextensions.k8s.io 77 | resources: 78 | - customresourcedefinitions 79 | verbs: 80 | - create 81 | - patch 82 | - modify 83 | - delete 84 | - deletecollection 85 | ` 86 | json, err := yaml.YAMLToJSON([]byte(data)) 87 | if err != nil { 88 | t.Fatal(err.Error()) 89 | } 90 | 91 | rbac := CustomResourceClusterRole(json) 92 | if rbac != 1 { 93 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 94 | } 95 | } 96 | 97 | func Test_CRD_Multiple_Resources(t *testing.T) { 98 | var data = ` 99 | --- 100 | apiVersion: rbac.authorization.k8s.io/v1 101 | kind: ClusterRole 102 | metadata: 103 | name: example-operator 104 | rules: 105 | - apiGroups: 106 | - apps 107 | - apiextensions.k8s.io 108 | resources: 109 | - deployment 110 | - customresourcedefinitions 111 | verbs: 112 | - create 113 | - patch 114 | - modify 115 | - delete 116 | - deletecollection 117 | ` 118 | json, err := yaml.YAMLToJSON([]byte(data)) 119 | if err != nil { 120 | t.Fatal(err.Error()) 121 | } 122 | 123 | rbac := CustomResourceClusterRole(json) 124 | if rbac != 1 { 125 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 126 | } 127 | } 128 | 129 | func Test_CRD_Multiple_Rules(t *testing.T) { 130 | var data = ` 131 | --- 132 | apiVersion: rbac.authorization.k8s.io/v1 133 | kind: ClusterRole 134 | metadata: 135 | name: example-operator 136 | rules: 137 | - apiGroups: 138 | - apiextensions.k8s.io 139 | resources: 140 | - customresourcedefinitions 141 | verbs: 142 | - create 143 | - patch 144 | - modify 145 | - delete 146 | - deletecollection 147 | - apiGroups: 148 | - apps 149 | resources: 150 | - deployment 151 | verbs: 152 | - create 153 | - patch 154 | - modify 155 | - delete 156 | - deletecollection 157 | ` 158 | json, err := yaml.YAMLToJSON([]byte(data)) 159 | if err != nil { 160 | t.Fatal(err.Error()) 161 | } 162 | 163 | rbac := CustomResourceClusterRole(json) 164 | if rbac != 1 { 165 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /pkg/rules/defaultNamespace.go: -------------------------------------------------------------------------------- 1 | // OPR-R1-NS - default namespace 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func DefaultNamespace(json []byte) int { 13 | namespace := 0 14 | 15 | jqNS := gojsonq.New().Reader(bytes.NewReader(json)). 16 | From("metadata.name").Get() 17 | 18 | jqDeploy := gojsonq.New().Reader(bytes.NewReader(json)). 19 | From("metadata.namespace").Get() 20 | 21 | jqCRB := gojsonq.New().Reader(bytes.NewReader(json)). 22 | From("subjects"). 23 | Only("namespace") 24 | 25 | if strings.Contains(fmt.Sprintf("%v", jqNS), "default") || 26 | strings.Contains(fmt.Sprintf("%v", jqDeploy), "default") || 27 | strings.Contains(fmt.Sprintf("%v", jqCRB), "default") { 28 | namespace++ 29 | } 30 | 31 | return namespace 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/defaultNamespace_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | // -- TODO KGW Write tests 9 | 10 | func Test_NonDefaultNamespace(t *testing.T) { 11 | var data = ` 12 | --- 13 | apiVersion: v1 14 | kind: Namespace 15 | metadata: 16 | name: system 17 | ` 18 | 19 | json, err := yaml.YAMLToJSON([]byte(data)) 20 | if err != nil { 21 | t.Fatal(err.Error()) 22 | } 23 | 24 | namespace := DefaultNamespace(json) 25 | if namespace != 0 { 26 | t.Errorf("Got %v namespace wanted %v", namespace, 0) 27 | } 28 | } 29 | 30 | func Test_DefaultNamespace(t *testing.T) { 31 | var data = ` 32 | --- 33 | apiVersion: v1 34 | kind: Namespace 35 | metadata: 36 | name: default 37 | ` 38 | 39 | json, err := yaml.YAMLToJSON([]byte(data)) 40 | if err != nil { 41 | t.Fatal(err.Error()) 42 | } 43 | 44 | namespace := DefaultNamespace(json) 45 | if namespace != 1 { 46 | t.Errorf("Got %v namespace wanted %v", namespace, 1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pkg/rules/escalateClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R16-RBAC - ClusterRole has escalate permissions 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func EscalateClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("rbac.authorization.k8s.io", rule.APIGroups) && 21 | contains("clusterroles", rule.Resources) && 22 | contains("escalate", rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | 29 | } 30 | -------------------------------------------------------------------------------- /pkg/rules/escalateClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Escalate_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - rbac.authorization.k8s.io 19 | resources: 20 | - clusterroles 21 | verbs: 22 | - escalate 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := EscalateClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Escalate_Permissions_Multiple_Rules(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - rbac.authorization.k8s.io 46 | resources: 47 | - clusterroles 48 | verbs: 49 | - escalate 50 | - apiGroups: 51 | - "" 52 | resources: 53 | - pods 54 | verbs: 55 | - "*" 56 | ` 57 | 58 | json, err := yaml.YAMLToJSON([]byte(data)) 59 | if err != nil { 60 | t.Fatal(err.Error()) 61 | } 62 | 63 | rbac := EscalateClusterRole(json) 64 | if rbac != 1 { 65 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 66 | } 67 | } 68 | func Test_Incorrect_Escalate_Permissions(t *testing.T) { 69 | var data = ` 70 | --- 71 | apiVersion: rbac.authorization.k8s.io/v1 72 | kind: ClusterRole 73 | metadata: 74 | name: example-operator 75 | rules: 76 | - apiGroups: 77 | - rbac.authorization.k8s.io 78 | resources: 79 | - serviceaccounts 80 | verbs: 81 | - escalate 82 | ` 83 | 84 | json, err := yaml.YAMLToJSON([]byte(data)) 85 | if err != nil { 86 | t.Fatal(err.Error()) 87 | } 88 | 89 | rbac := EscalateClusterRole(json) 90 | if rbac != 0 { 91 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pkg/rules/execPodsClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R15-RBAC - ClusterRole can exec into Pods 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func ExecPodsClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | var foundPodsGet, foundExecCreate bool 14 | 15 | clusterRole := &rbacv1.ClusterRole{} 16 | err := json.Unmarshal(input, clusterRole) 17 | if err != nil { 18 | return 0 19 | } 20 | 21 | for _, rule := range clusterRole.Rules { 22 | if contains("", rule.APIGroups) && 23 | containsAll([]string{"pods", "pods/exec"}, rule.Resources) && 24 | (contains("*", rule.Verbs) || containsAll([]string{"get", "create"}, rule.Verbs)) { 25 | rbac++ 26 | } else if contains("", rule.APIGroups) && 27 | contains("pods", rule.Resources) && 28 | containsAny([]string{"*", "get"}, rule.Verbs) { 29 | foundPodsGet = true 30 | if foundPodsGet && foundExecCreate { 31 | rbac++ 32 | } 33 | } else if contains("", rule.APIGroups) && 34 | contains("pods/exec", rule.Resources) && 35 | containsAny([]string{"*", "create"}, rule.Verbs) { 36 | foundExecCreate = true 37 | if foundPodsGet && foundExecCreate { 38 | rbac++ 39 | } 40 | } 41 | 42 | } 43 | 44 | return rbac 45 | } 46 | -------------------------------------------------------------------------------- /pkg/rules/execPodsClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Pods_All_Star_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - pods 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := ExecPodsClusterRole(json) 31 | if rbac != 0 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 33 | } 34 | } 35 | 36 | func Test_Incorrect_Pod_Exec_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - pod/exec 48 | verbs: 49 | - create 50 | - delete 51 | - deletecollection 52 | - patch 53 | - update 54 | ` 55 | 56 | json, err := yaml.YAMLToJSON([]byte(data)) 57 | if err != nil { 58 | t.Fatal(err.Error()) 59 | } 60 | 61 | rbac := ExecPodsClusterRole(json) 62 | if rbac != 0 { 63 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 64 | } 65 | } 66 | 67 | func Test_Pods_Pods_Exec_Verbs_Permissions(t *testing.T) { 68 | var data = ` 69 | --- 70 | apiVersion: rbac.authorization.k8s.io/v1 71 | kind: ClusterRole 72 | metadata: 73 | name: example-operator 74 | rules: 75 | - apiGroups: 76 | - "" 77 | resources: 78 | - pods 79 | - pods/exec 80 | verbs: 81 | - get 82 | - create 83 | ` 84 | json, err := yaml.YAMLToJSON([]byte(data)) 85 | if err != nil { 86 | t.Fatal(err.Error()) 87 | } 88 | 89 | rbac := ExecPodsClusterRole(json) 90 | if rbac != 1 { 91 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 92 | } 93 | } 94 | 95 | func Test_Pods_Exec_Verbs_Create_Only(t *testing.T) { 96 | var data = ` 97 | --- 98 | apiVersion: rbac.authorization.k8s.io/v1 99 | kind: ClusterRole 100 | metadata: 101 | name: example-operator 102 | rules: 103 | - apiGroups: 104 | - "" 105 | resources: 106 | - pods/exec 107 | verbs: 108 | - create 109 | ` 110 | json, err := yaml.YAMLToJSON([]byte(data)) 111 | if err != nil { 112 | t.Fatal(err.Error()) 113 | } 114 | 115 | rbac := ExecPodsClusterRole(json) 116 | if rbac != 0 { 117 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 118 | } 119 | } 120 | 121 | func Test_Pods_Exec_Verbs_Get_Only(t *testing.T) { 122 | var data = ` 123 | --- 124 | apiVersion: rbac.authorization.k8s.io/v1 125 | kind: ClusterRole 126 | metadata: 127 | name: example-operator 128 | rules: 129 | - apiGroups: 130 | - "" 131 | resources: 132 | - pods/exec 133 | verbs: 134 | - get 135 | ` 136 | json, err := yaml.YAMLToJSON([]byte(data)) 137 | if err != nil { 138 | t.Fatal(err.Error()) 139 | } 140 | 141 | rbac := ExecPodsClusterRole(json) 142 | if rbac != 0 { 143 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 144 | } 145 | } 146 | 147 | func Test_Pods_Exec_Verbs_Split_Rules(t *testing.T) { 148 | var data = ` 149 | --- 150 | apiVersion: rbac.authorization.k8s.io/v1 151 | kind: ClusterRole 152 | metadata: 153 | name: example-operator 154 | rules: 155 | - apiGroups: 156 | - "" 157 | resources: 158 | - pods 159 | verbs: 160 | - get 161 | - apiGroups: 162 | - "" 163 | resources: 164 | - pods/exec 165 | verbs: 166 | - create 167 | ` 168 | json, err := yaml.YAMLToJSON([]byte(data)) 169 | if err != nil { 170 | t.Fatal(err.Error()) 171 | } 172 | 173 | rbac := ExecPodsClusterRole(json) 174 | if rbac != 1 { 175 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 176 | } 177 | } 178 | 179 | func Test_Pods_Verbs_Get_Only(t *testing.T) { 180 | var data = ` 181 | --- 182 | apiVersion: rbac.authorization.k8s.io/v1 183 | kind: ClusterRole 184 | metadata: 185 | name: example-operator 186 | rules: 187 | - apiGroups: 188 | - "" 189 | resources: 190 | - pods 191 | verbs: 192 | - get 193 | ` 194 | json, err := yaml.YAMLToJSON([]byte(data)) 195 | if err != nil { 196 | t.Fatal(err.Error()) 197 | } 198 | 199 | rbac := ExecPodsClusterRole(json) 200 | if rbac != 0 { 201 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 202 | } 203 | } 204 | 205 | func Test_Pods_Pods_Exec_Star_Permissions(t *testing.T) { 206 | var data = ` 207 | --- 208 | apiVersion: rbac.authorization.k8s.io/v1 209 | kind: ClusterRole 210 | metadata: 211 | name: example-operator 212 | rules: 213 | - apiGroups: 214 | - "" 215 | resources: 216 | - pods 217 | - pods/exec 218 | verbs: 219 | - "*" 220 | ` 221 | json, err := yaml.YAMLToJSON([]byte(data)) 222 | if err != nil { 223 | t.Fatal(err.Error()) 224 | } 225 | 226 | rbac := ExecPodsClusterRole(json) 227 | if rbac != 1 { 228 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /pkg/rules/impersonateClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R18-RBAC - ClusterRole has impersonate permissions 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func ImpersonateClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if containsAny([]string{"", "*"}, rule.APIGroups) && 21 | contains("serviceaccounts", rule.Resources) && 22 | contains("impersonate", rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rules/impersonateClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Impersonate_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - serviceaccounts 21 | verbs: 22 | - impersonate 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := ImpersonateClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Impersonate_Permissions_Multiple_Rules(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - serviceaccounts 48 | verbs: 49 | - impersonate 50 | - apiGroups: 51 | - "apps" 52 | resources: 53 | - deployments 54 | verbs: 55 | - "*" 56 | ` 57 | 58 | json, err := yaml.YAMLToJSON([]byte(data)) 59 | if err != nil { 60 | t.Fatal(err.Error()) 61 | } 62 | 63 | rbac := ImpersonateClusterRole(json) 64 | if rbac != 1 { 65 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 66 | } 67 | } 68 | 69 | func Test_Incorrect_Impersonate_Permissions(t *testing.T) { 70 | var data = ` 71 | --- 72 | apiVersion: rbac.authorization.k8s.io/v1 73 | kind: ClusterRole 74 | metadata: 75 | name: example-operator 76 | rules: 77 | - apiGroups: 78 | - rbac.authorization.k8s.io 79 | resources: 80 | - serviceaccounts 81 | verbs: 82 | - impersonate 83 | ` 84 | 85 | json, err := yaml.YAMLToJSON([]byte(data)) 86 | if err != nil { 87 | t.Fatal(err.Error()) 88 | } 89 | 90 | rbac := ImpersonateClusterRole(json) 91 | if rbac != 0 { 92 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/rules/kubesystemNamespace.go: -------------------------------------------------------------------------------- 1 | // OPR-R2-NS - kube-system namespace 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func KubeSystemNamespace(json []byte) int { 13 | namespace := 0 14 | 15 | jqNS := gojsonq.New().Reader(bytes.NewReader(json)). 16 | From("metadata.name").Get() 17 | 18 | jqDeploy := gojsonq.New().Reader(bytes.NewReader(json)). 19 | From("metadata.namespace").Get() 20 | 21 | jqCRB := gojsonq.New().Reader(bytes.NewReader(json)). 22 | From("subjects"). 23 | Only("namespace") 24 | 25 | if strings.Contains(fmt.Sprintf("%v", jqNS), "kube-system") || 26 | strings.Contains(fmt.Sprintf("%v", jqDeploy), "kube-system") || 27 | strings.Contains(fmt.Sprintf("%v", jqCRB), "kube-system") { 28 | namespace++ 29 | } 30 | 31 | return namespace 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/kubesystemNamespace_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | // -- TODO KGW Write tests 10 | 11 | func Test_NonKubeSystemNamespace(t *testing.T) { 12 | var data = ` 13 | --- 14 | apiVersion: v1 15 | kind: Namespace 16 | metadata: 17 | name: system 18 | ` 19 | 20 | json, err := yaml.YAMLToJSON([]byte(data)) 21 | if err != nil { 22 | t.Fatal(err.Error()) 23 | } 24 | 25 | namespace := KubeSystemNamespace(json) 26 | if namespace != 0 { 27 | t.Errorf("Got %v namespace wanted %v", namespace, 0) 28 | } 29 | } 30 | 31 | func Test_KubeSystemNamespace(t *testing.T) { 32 | var data = ` 33 | --- 34 | apiVersion: v1 35 | kind: Namespace 36 | metadata: 37 | name: kube-system 38 | ` 39 | 40 | json, err := yaml.YAMLToJSON([]byte(data)) 41 | if err != nil { 42 | t.Fatal(err.Error()) 43 | } 44 | 45 | namespace := KubeSystemNamespace(json) 46 | if namespace != 1 { 47 | t.Errorf("Got %v namespace wanted %v", namespace, 0) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/rules/modifyPodLogsClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R19-RBAC - ClusterRole can modify pod logs 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func ModifyPodLogsClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("", rule.APIGroups) && 21 | contains("pods/log", rule.Resources) && 22 | containsAny([]string{"*", "create", "patch", "update", "delete", "deletecollection"}, rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rules/modifyPodLogsClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Pods_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - pods 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := ModifyPodLogsClusterRole(json) 31 | if rbac != 0 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 33 | } 34 | } 35 | 36 | func Test_Incorrect_Pod_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - pod 48 | verbs: 49 | - create 50 | - delete 51 | - deletecollection 52 | - patch 53 | - update 54 | ` 55 | 56 | json, err := yaml.YAMLToJSON([]byte(data)) 57 | if err != nil { 58 | t.Fatal(err.Error()) 59 | } 60 | 61 | rbac := ModifyPodLogsClusterRole(json) 62 | if rbac != 0 { 63 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 64 | } 65 | } 66 | 67 | func Test_Pods_Log_Verbs_Permissions(t *testing.T) { 68 | var data = ` 69 | --- 70 | apiVersion: rbac.authorization.k8s.io/v1 71 | kind: ClusterRole 72 | metadata: 73 | name: example-operator 74 | rules: 75 | - apiGroups: 76 | - "" 77 | resources: 78 | - pods/log 79 | verbs: 80 | - create 81 | - delete 82 | - deletecollection 83 | - patch 84 | - update 85 | ` 86 | json, err := yaml.YAMLToJSON([]byte(data)) 87 | if err != nil { 88 | t.Fatal(err.Error()) 89 | } 90 | 91 | rbac := ModifyPodLogsClusterRole(json) 92 | if rbac != 1 { 93 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /pkg/rules/networkPolicyClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R25-RBAC - ClusterRole has read, write or delete permissions over network policies 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func NetworkPolicyClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("networking.k8s.io", rule.APIGroups) && 21 | containsAny([]string{"networkpolicy", "networkpolicies", "*"}, rule.Resources) && 22 | containsAny([]string{"*", "create", "update", "patch", "delete", "deletecollection"}, rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rules/networkPolicyClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Network_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "networking.k8s.io" 19 | resources: 20 | - "*" 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := NetworkPolicyClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Incorrect_Network_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - "*" 48 | verbs: 49 | - "*" 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := NetworkPolicyClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_Network_Policy_Verbs_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - "networking.k8s.io" 73 | resources: 74 | - "networkpolicy" 75 | verbs: 76 | - create 77 | - update 78 | - delete 79 | - patch 80 | - deletecollection 81 | ` 82 | json, err := yaml.YAMLToJSON([]byte(data)) 83 | if err != nil { 84 | t.Fatal(err.Error()) 85 | } 86 | 87 | rbac := NetworkPolicyClusterRole(json) 88 | if rbac != 1 { 89 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 90 | } 91 | } 92 | 93 | func Test_Network_Multiple_API_Groups(t *testing.T) { 94 | var data = ` 95 | --- 96 | apiVersion: rbac.authorization.k8s.io/v1 97 | kind: ClusterRole 98 | metadata: 99 | name: example-operator 100 | rules: 101 | - apiGroups: 102 | - apps 103 | - "networking.k8s.io" 104 | resources: 105 | - "*" 106 | verbs: 107 | - "*" 108 | ` 109 | 110 | json, err := yaml.YAMLToJSON([]byte(data)) 111 | if err != nil { 112 | t.Fatal(err.Error()) 113 | } 114 | 115 | rbac := NetworkPolicyClusterRole(json) 116 | if rbac != 1 { 117 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 118 | } 119 | } 120 | 121 | func Test_Network_Multiple_Rules(t *testing.T) { 122 | var data = ` 123 | --- 124 | apiVersion: rbac.authorization.k8s.io/v1 125 | kind: ClusterRole 126 | metadata: 127 | name: example-operator 128 | rules: 129 | - apiGroups: 130 | - "networking.k8s.io" 131 | resources: 132 | - "*" 133 | verbs: 134 | - "*" 135 | - apiGroups: 136 | - "apps" 137 | resources: 138 | - "*" 139 | verbs: 140 | - "*" 141 | ` 142 | 143 | json, err := yaml.YAMLToJSON([]byte(data)) 144 | if err != nil { 145 | t.Fatal(err.Error()) 146 | } 147 | 148 | rbac := NetworkPolicyClusterRole(json) 149 | if rbac != 1 { 150 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 151 | } 152 | } 153 | 154 | func Test_NetworkPolicies(t *testing.T) { 155 | var data = ` 156 | --- 157 | apiVersion: rbac.authorization.k8s.io/v1 158 | kind: ClusterRole 159 | metadata: 160 | name: example-operator 161 | rules: 162 | - apiGroups: 163 | - "networking.k8s.io" 164 | resources: 165 | - "networkpolicies" 166 | verbs: 167 | - "*" 168 | ` 169 | 170 | json, err := yaml.YAMLToJSON([]byte(data)) 171 | if err != nil { 172 | t.Fatal(err.Error()) 173 | } 174 | 175 | rbac := NetworkPolicyClusterRole(json) 176 | if rbac != 1 { 177 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /pkg/rules/noSecurityContext.go: -------------------------------------------------------------------------------- 1 | // OPR-R3-SC - No securityContext 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | 7 | "github.com/thedevsaddam/gojsonq/v2" 8 | ) 9 | 10 | func NoSecurityContext(json []byte) int { 11 | spec := getSpecSelector(json) 12 | sc := 0 13 | 14 | jqContainers := gojsonq.New().Reader(bytes.NewReader(json)). 15 | From(spec + ".containers"). 16 | Select("securityContext") 17 | 18 | jqSecurityContext := gojsonq.New().Reader(bytes.NewReader(json)). 19 | From(spec + ".securityContext") 20 | 21 | if jqContainers.Count() == 0 && jqSecurityContext.Count() == 0 { 22 | sc++ 23 | } 24 | 25 | return sc 26 | } 27 | -------------------------------------------------------------------------------- /pkg/rules/noSecurityContext_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | func Test_ContainersSecurityContext(t *testing.T) { 9 | var data = ` 10 | --- 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | spec: 14 | template: 15 | spec: 16 | containers: 17 | - name: c1 18 | securityContext: 19 | allowPrivilegeEscalation: false 20 | ` 21 | 22 | json, err := yaml.YAMLToJSON([]byte(data)) 23 | if err != nil { 24 | t.Fatal(err.Error()) 25 | } 26 | 27 | securityContext := NoSecurityContext(json) 28 | if securityContext != 0 { 29 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 30 | } 31 | } 32 | 33 | func Test_NoContainersSecurityContext(t *testing.T) { 34 | var data = ` 35 | --- 36 | apiVersion: apps/v1 37 | kind: Deployment 38 | spec: 39 | template: 40 | spec: 41 | containers: 42 | - name: c1 43 | ` 44 | 45 | json, err := yaml.YAMLToJSON([]byte(data)) 46 | if err != nil { 47 | t.Fatal(err.Error()) 48 | } 49 | 50 | securityContext := NoSecurityContext(json) 51 | if securityContext != 1 { 52 | t.Errorf("Got %v securityContext wanted %v", securityContext, 1) 53 | } 54 | } 55 | 56 | func Test_SpecSecurityContext(t *testing.T) { 57 | var data = ` 58 | --- 59 | apiVersion: apps/v1 60 | kind: Deployment 61 | spec: 62 | template: 63 | spec: 64 | securityContext: 65 | allowPrivilegeEscalation: false 66 | ` 67 | 68 | json, err := yaml.YAMLToJSON([]byte(data)) 69 | if err != nil { 70 | t.Fatal(err.Error()) 71 | } 72 | 73 | securityContext := NoSecurityContext(json) 74 | if securityContext != 0 { 75 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 76 | } 77 | } 78 | 79 | func Test_NoSpecSecurityContext(t *testing.T) { 80 | var data = ` 81 | --- 82 | apiVersion: apps/v1 83 | kind: Deployment 84 | spec: 85 | template: 86 | spec: 87 | ` 88 | 89 | json, err := yaml.YAMLToJSON([]byte(data)) 90 | if err != nil { 91 | t.Fatal(err.Error()) 92 | } 93 | 94 | securityContext := NoSecurityContext(json) 95 | if securityContext != 1 { 96 | t.Errorf("Got %v securityContext wanted %v", securityContext, 1) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /pkg/rules/nodeProxyClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R26-RBAC - ClusterRole has permissions over the Kubernetes API server proxy 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func NodeProxyClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("", rule.APIGroups) && 21 | contains("nodes/proxy", rule.Resources) && 22 | contains("*", rule.Verbs) { 23 | rbac++ 24 | } else if contains("", rule.APIGroups) && 25 | contains("nodes/proxy", rule.Resources) && 26 | containsAll([]string{"get", "create"}, rule.Verbs) { 27 | rbac++ 28 | } 29 | } 30 | 31 | return rbac 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/nodeProxyClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Nodes_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - nodes/proxy 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := NodeProxyClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Incorrect_Node_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - node 48 | verbs: 49 | - "*" 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := NodeProxyClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_Nodes_Proxy_Verbs_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - "" 73 | resources: 74 | - nodes/proxy 75 | verbs: 76 | - get 77 | - create 78 | ` 79 | json, err := yaml.YAMLToJSON([]byte(data)) 80 | if err != nil { 81 | t.Fatal(err.Error()) 82 | } 83 | 84 | rbac := NodeProxyClusterRole(json) 85 | if rbac != 1 { 86 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 87 | } 88 | } 89 | 90 | func Test_Nodes_Proxy_Multiple_Rules(t *testing.T) { 91 | var data = ` 92 | --- 93 | apiVersion: rbac.authorization.k8s.io/v1 94 | kind: ClusterRole 95 | metadata: 96 | name: example-operator 97 | rules: 98 | - apiGroups: 99 | - "" 100 | resources: 101 | - nodes/proxy 102 | verbs: 103 | - get 104 | - create 105 | - apiGroups: 106 | - apps 107 | resources: 108 | - deployments 109 | verbs: 110 | - "*" 111 | ` 112 | json, err := yaml.YAMLToJSON([]byte(data)) 113 | if err != nil { 114 | t.Fatal(err.Error()) 115 | } 116 | 117 | rbac := NodeProxyClusterRole(json) 118 | if rbac != 1 { 119 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 120 | } 121 | } 122 | 123 | func Test_Nodes_Proxy_Multiple_API_Groups(t *testing.T) { 124 | var data = ` 125 | --- 126 | apiVersion: rbac.authorization.k8s.io/v1 127 | kind: ClusterRole 128 | metadata: 129 | name: example-operator 130 | rules: 131 | - apiGroups: 132 | - "" 133 | - apps 134 | resources: 135 | - nodes/proxy 136 | - deployments 137 | verbs: 138 | - get 139 | - create 140 | ` 141 | json, err := yaml.YAMLToJSON([]byte(data)) 142 | if err != nil { 143 | t.Fatal(err.Error()) 144 | } 145 | 146 | rbac := NodeProxyClusterRole(json) 147 | if rbac != 1 { 148 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /pkg/rules/persistentVolumesClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R24-RBAC - ClusterRole has read, write or delete permissions over persistent volumes 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func PersistentVolumeClusterRole(input []byte) int { 11 | rbac := 0 12 | var foundPV, foundPVC bool 13 | 14 | clusterRole := &rbacv1.ClusterRole{} 15 | err := json.Unmarshal(input, clusterRole) 16 | if err != nil { 17 | return 0 18 | } 19 | 20 | for _, rule := range clusterRole.Rules { 21 | if contains("", rule.APIGroups) && 22 | containsAll([]string{"persistentvolumes", "persistentvolumeclaims"}, rule.Resources) && 23 | containsAny([]string{"*", "get", "list", "create", "patch", "update", "delete", "deletecollection", "watch"}, rule.Verbs) { 24 | rbac++ 25 | } else if contains("", rule.APIGroups) && 26 | contains("persistentvolumes", rule.Resources) && 27 | containsAny([]string{"*", "get", "list", "create", "patch", "update", "delete", "deletecollection", "watch"}, rule.Verbs) { 28 | foundPV = true 29 | if foundPV && foundPVC { 30 | rbac++ 31 | } 32 | } else if contains("", rule.APIGroups) && 33 | contains("persistentvolumeclaims", rule.Resources) && 34 | containsAny([]string{"*", "get", "list", "create", "patch", "update", "delete", "deletecollection", "watch"}, rule.Verbs) { 35 | foundPVC = true 36 | if foundPV && foundPVC { 37 | rbac++ 38 | } 39 | } 40 | } 41 | 42 | return rbac 43 | } 44 | -------------------------------------------------------------------------------- /pkg/rules/persistentVolumesClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_PVC_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - persistentvolumes 21 | - persistentvolumeclaims 22 | verbs: 23 | - "*" 24 | ` 25 | 26 | json, err := yaml.YAMLToJSON([]byte(data)) 27 | if err != nil { 28 | t.Fatal(err.Error()) 29 | } 30 | 31 | rbac := PersistentVolumeClusterRole(json) 32 | if rbac != 1 { 33 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 34 | } 35 | } 36 | 37 | func Test_Incorrect_PVC_Permissions(t *testing.T) { 38 | var data = ` 39 | --- 40 | apiVersion: rbac.authorization.k8s.io/v1 41 | kind: ClusterRole 42 | metadata: 43 | name: example-operator 44 | rules: 45 | - apiGroups: 46 | - authorization.k8s.io 47 | resources: 48 | - persistentvolumeclaims 49 | verbs: 50 | - "*" 51 | ` 52 | 53 | json, err := yaml.YAMLToJSON([]byte(data)) 54 | if err != nil { 55 | t.Fatal(err.Error()) 56 | } 57 | 58 | rbac := PersistentVolumeClusterRole(json) 59 | if rbac != 0 { 60 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 61 | } 62 | } 63 | 64 | func Test_PVC_Verbs_Permissions(t *testing.T) { 65 | var data = ` 66 | --- 67 | apiVersion: rbac.authorization.k8s.io/v1 68 | kind: ClusterRole 69 | metadata: 70 | name: example-operator 71 | rules: 72 | - apiGroups: 73 | - "" 74 | resources: 75 | - persistentvolumes 76 | - persistentvolumeclaims 77 | verbs: 78 | - delete 79 | - deletecollection 80 | - create 81 | - patch 82 | - get 83 | - list 84 | - update 85 | - watch 86 | ` 87 | json, err := yaml.YAMLToJSON([]byte(data)) 88 | if err != nil { 89 | t.Fatal(err.Error()) 90 | } 91 | 92 | rbac := PersistentVolumeClusterRole(json) 93 | if rbac != 1 { 94 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 95 | } 96 | } 97 | 98 | func Test_PVC_Multiple_Rules(t *testing.T) { 99 | var data = ` 100 | --- 101 | apiVersion: rbac.authorization.k8s.io/v1 102 | kind: ClusterRole 103 | metadata: 104 | name: example-operator 105 | rules: 106 | - apiGroups: 107 | - "" 108 | resources: 109 | - persistentvolumes 110 | - persistentvolumeclaims 111 | verbs: 112 | - delete 113 | - deletecollection 114 | - create 115 | - patch 116 | - get 117 | - list 118 | - update 119 | - watch 120 | - apiGroups: 121 | - apps 122 | resources: 123 | - deployments 124 | verbs: 125 | - delete 126 | - deletecollection 127 | - create 128 | - patch 129 | - get 130 | - list 131 | - update 132 | - watch 133 | ` 134 | json, err := yaml.YAMLToJSON([]byte(data)) 135 | if err != nil { 136 | t.Fatal(err.Error()) 137 | } 138 | 139 | rbac := PersistentVolumeClusterRole(json) 140 | if rbac != 1 { 141 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 142 | } 143 | } 144 | 145 | func Test_PVC_Multiple_API_Groups(t *testing.T) { 146 | var data = ` 147 | --- 148 | apiVersion: rbac.authorization.k8s.io/v1 149 | kind: ClusterRole 150 | metadata: 151 | name: example-operator 152 | rules: 153 | - apiGroups: 154 | - "" 155 | - apps 156 | resources: 157 | - persistentvolumes 158 | - persistentvolumeclaims 159 | - deployments 160 | verbs: 161 | - delete 162 | - deletecollection 163 | - create 164 | - patch 165 | - get 166 | - list 167 | - update 168 | - watch 169 | ` 170 | json, err := yaml.YAMLToJSON([]byte(data)) 171 | if err != nil { 172 | t.Fatal(err.Error()) 173 | } 174 | 175 | rbac := PersistentVolumeClusterRole(json) 176 | if rbac != 1 { 177 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 178 | } 179 | } 180 | 181 | func Test_PVC_Separate_Resources(t *testing.T) { 182 | var data = ` 183 | --- 184 | apiVersion: rbac.authorization.k8s.io/v1 185 | kind: ClusterRole 186 | metadata: 187 | name: example-operator 188 | rules: 189 | - apiGroups: 190 | - "" 191 | resources: 192 | - persistentvolumes 193 | verbs: 194 | - delete 195 | - deletecollection 196 | - create 197 | - patch 198 | - get 199 | - list 200 | - update 201 | - watch 202 | - apiGroups: 203 | - "" 204 | resources: 205 | - persistentvolumeclaims 206 | verbs: 207 | - delete 208 | - deletecollection 209 | - create 210 | - patch 211 | - get 212 | - list 213 | - update 214 | - watch 215 | ` 216 | json, err := yaml.YAMLToJSON([]byte(data)) 217 | if err != nil { 218 | t.Fatal(err.Error()) 219 | } 220 | 221 | rbac := PersistentVolumeClusterRole(json) 222 | if rbac != 1 { 223 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /pkg/rules/privileged.go: -------------------------------------------------------------------------------- 1 | // OPR-R5-SC - securityContext set to privileged: true 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func Privileged(json []byte) int { 13 | sc := 0 14 | spec := getSpecSelector(json) 15 | 16 | jqContainers := gojsonq.New().Reader(bytes.NewReader(json)). 17 | From(spec+".containers"). 18 | Where("securityContext", "!=", nil). 19 | Where("securityContext.privileged", "!=", nil). 20 | Where("securityContext.privileged", "=", true) 21 | 22 | jqSecurityContext := gojsonq.New().Reader(bytes.NewReader(json)). 23 | From(spec+".securityContext"). 24 | Where("securityContext", "!=", nil). 25 | Where("securityContext.privileged", "!=", nil) 26 | 27 | if strings.Contains(fmt.Sprintf("%v", jqSecurityContext.Get()), "privileged:true") { 28 | sc++ 29 | } 30 | 31 | return jqContainers.Count() + sc 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/privileged_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | func Test_Privileged_Pod(t *testing.T) { 9 | var data = ` 10 | --- 11 | apiVersion: v1 12 | kind: Pod 13 | spec: 14 | containers: 15 | - name: c1 16 | securityContext: 17 | privileged: true 18 | ` 19 | 20 | json, err := yaml.YAMLToJSON([]byte(data)) 21 | if err != nil { 22 | t.Fatal(err.Error()) 23 | } 24 | 25 | securityContext := Privileged(json) 26 | if securityContext != 1 { 27 | t.Errorf("Got %v securityContext wanted %v", securityContext, 1) 28 | } 29 | } 30 | 31 | func Test_Privileged_Missing(t *testing.T) { 32 | var data = ` 33 | --- 34 | apiVersion: v1 35 | kind: Pod 36 | spec: 37 | containers: 38 | - name: c1 39 | securityContext: 40 | - name: c2 41 | ` 42 | 43 | json, err := yaml.YAMLToJSON([]byte(data)) 44 | if err != nil { 45 | t.Fatal(err.Error()) 46 | } 47 | 48 | securityContext := Privileged(json) 49 | if securityContext != 0 { 50 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 51 | } 52 | } 53 | 54 | func Test_Privileged_Deploy_Spec(t *testing.T) { 55 | var data = ` 56 | apiVersion: apps/v1 57 | kind: Deployment 58 | metadata: 59 | name: controller-manager 60 | namespace: system 61 | labels: 62 | control-plane: controller-manager 63 | spec: 64 | selector: 65 | matchLabels: 66 | control-plane: controller-manager 67 | replicas: 1 68 | template: 69 | metadata: 70 | annotations: 71 | kubectl.kubernetes.io/default-container: manager 72 | labels: 73 | control-plane: controller-manager 74 | spec: 75 | securityContext: 76 | privileged: false 77 | ` 78 | 79 | json, err := yaml.YAMLToJSON([]byte(data)) 80 | if err != nil { 81 | t.Fatal(err.Error()) 82 | } 83 | 84 | securityContext := Privileged(json) 85 | if securityContext != 0 { 86 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkg/rules/readOnlyRootFilesystem.go: -------------------------------------------------------------------------------- 1 | // OPR-R6-SC - securityContext set to readOnlyRootFilesystem: false 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func ReadOnlyRootFilesystem(json []byte) int { 13 | sc := 0 14 | spec := getSpecSelector(json) 15 | 16 | jqContainers := gojsonq.New().Reader(bytes.NewReader(json)). 17 | From(spec+".containers"). 18 | Where("securityContext", "!=", nil). 19 | Where("securityContext.readOnlyRootFilesystem", "!=", nil). 20 | Where("securityContext.readOnlyRootFilesystem", "=", false) 21 | 22 | jqSecurityContext := gojsonq.New().Reader(bytes.NewReader(json)). 23 | From(spec+".securityContext"). 24 | Where("securityContext", "!=", nil). 25 | Where("securityContext.readOnlyRootFilesystem", "!=", nil) 26 | 27 | if strings.Contains(fmt.Sprintf("%v", jqSecurityContext.Get()), "readOnlyRootFilesystem:false") { 28 | sc++ 29 | } 30 | 31 | return jqContainers.Count() + sc 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/readOnlyRootFilesystem_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | func Test_ReadOnlyRootFilesystem(t *testing.T) { 9 | var data = ` 10 | --- 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | spec: 14 | template: 15 | spec: 16 | containers: 17 | - name: c1 18 | - name: c2 19 | securityContext: 20 | readOnlyRootFilesystem: false 21 | - name: c3 22 | securityContext: 23 | readOnlyRootFilesystem: true 24 | ` 25 | 26 | json, err := yaml.YAMLToJSON([]byte(data)) 27 | if err != nil { 28 | t.Fatal(err.Error()) 29 | } 30 | 31 | containers := ReadOnlyRootFilesystem(json) 32 | if containers != 1 { 33 | t.Errorf("Got %v containers wanted %v", containers, 1) 34 | } 35 | } 36 | 37 | func Test_ReadOnlyRootFilesystem_NotSpecified(t *testing.T) { 38 | var data = ` 39 | --- 40 | apiVersion: v1 41 | kind: Pod 42 | metadata: 43 | name: security-context-demo 44 | spec: 45 | containers: 46 | - name: sec-ctx-demo 47 | image: gcr.io/google-samples/node-hello:1.0 48 | securityContext: 49 | capabilities: 50 | add: 51 | - CHOWN 52 | ` 53 | 54 | json, err := yaml.YAMLToJSON([]byte(data)) 55 | if err != nil { 56 | t.Fatal(err.Error()) 57 | } 58 | 59 | securityContext := ReadOnlyRootFilesystem(json) 60 | if securityContext != 0 { 61 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 62 | } 63 | } 64 | 65 | func Test_ReadOnlyRootFilesystem_NoContainers(t *testing.T) { 66 | var data = ` 67 | --- 68 | apiVersion: extensions/v1beta1 69 | kind: Deployment 70 | spec: 71 | template: 72 | spec: 73 | serviceAccountName: badrobot 74 | ` 75 | 76 | json, err := yaml.YAMLToJSON([]byte(data)) 77 | if err != nil { 78 | t.Fatal(err.Error()) 79 | } 80 | 81 | securityContext := ReadOnlyRootFilesystem(json) 82 | if securityContext != 0 { 83 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 84 | } 85 | } 86 | 87 | func Test_ReadOnlyRootFilesystem_Deploy_Spec(t *testing.T) { 88 | var data = ` 89 | apiVersion: apps/v1 90 | kind: Deployment 91 | metadata: 92 | name: controller-manager 93 | namespace: system 94 | labels: 95 | control-plane: controller-manager 96 | spec: 97 | selector: 98 | matchLabels: 99 | control-plane: controller-manager 100 | replicas: 1 101 | template: 102 | metadata: 103 | annotations: 104 | kubectl.kubernetes.io/default-container: manager 105 | labels: 106 | control-plane: controller-manager 107 | spec: 108 | securityContext: 109 | readOnlyRootFilesystem: true 110 | ` 111 | 112 | json, err := yaml.YAMLToJSON([]byte(data)) 113 | if err != nil { 114 | t.Fatal(err.Error()) 115 | } 116 | 117 | securityContext := ReadOnlyRootFilesystem(json) 118 | if securityContext != 0 { 119 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pkg/rules/removeEventsClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R20-RBAC - ClusterRole can remove Kubernetes events 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func RemoveEventsClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("", rule.APIGroups) && 21 | contains("events", rule.Resources) && 22 | containsAny([]string{"*", "delete", "deletecollection"}, rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rules/removeEventsClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Events_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - events 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := RemoveEventsClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Incorrect_Events_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - authorization.k8s.io 46 | resources: 47 | - events 48 | verbs: 49 | - "*" 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := RemoveEventsClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_Events_Verbs_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - "" 73 | resources: 74 | - events 75 | verbs: 76 | - delete 77 | - deletecollection 78 | ` 79 | json, err := yaml.YAMLToJSON([]byte(data)) 80 | if err != nil { 81 | t.Fatal(err.Error()) 82 | } 83 | 84 | rbac := RemoveEventsClusterRole(json) 85 | if rbac != 1 { 86 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 87 | } 88 | } 89 | 90 | func Test_Events_Multiple_Resources(t *testing.T) { 91 | var data = ` 92 | --- 93 | apiVersion: rbac.authorization.k8s.io/v1 94 | kind: ClusterRole 95 | metadata: 96 | name: example-operator 97 | rules: 98 | - apiGroups: 99 | - "" 100 | resources: 101 | - pods 102 | - events 103 | verbs: 104 | - "*" 105 | ` 106 | 107 | json, err := yaml.YAMLToJSON([]byte(data)) 108 | if err != nil { 109 | t.Fatal(err.Error()) 110 | } 111 | 112 | rbac := RemoveEventsClusterRole(json) 113 | if rbac != 1 { 114 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 115 | } 116 | } 117 | 118 | func Test_Events_Multiple_API_Groups(t *testing.T) { 119 | var data = ` 120 | --- 121 | apiVersion: rbac.authorization.k8s.io/v1 122 | kind: ClusterRole 123 | metadata: 124 | name: example-operator 125 | rules: 126 | - apiGroups: 127 | - "" 128 | - apps 129 | resources: 130 | - events 131 | - deployments 132 | verbs: 133 | - "*" 134 | ` 135 | 136 | json, err := yaml.YAMLToJSON([]byte(data)) 137 | if err != nil { 138 | t.Fatal(err.Error()) 139 | } 140 | 141 | rbac := RemoveEventsClusterRole(json) 142 | if rbac != 1 { 143 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 144 | } 145 | } 146 | 147 | func Test_Events_Multiple_Rules(t *testing.T) { 148 | var data = ` 149 | --- 150 | apiVersion: rbac.authorization.k8s.io/v1 151 | kind: ClusterRole 152 | metadata: 153 | name: example-operator 154 | rules: 155 | - apiGroups: 156 | - "" 157 | resources: 158 | - events 159 | verbs: 160 | - "*" 161 | - apiGroups: 162 | - "apps" 163 | resources: 164 | - deployments 165 | verbs: 166 | - "*" 167 | ` 168 | 169 | json, err := yaml.YAMLToJSON([]byte(data)) 170 | if err != nil { 171 | t.Fatal(err.Error()) 172 | } 173 | 174 | rbac := RemoveEventsClusterRole(json) 175 | if rbac != 1 { 176 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /pkg/rules/runAsNonRoot.go: -------------------------------------------------------------------------------- 1 | // OPR-R7-SC - securityContext set to runAsNonRoot: false 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func RunAsNonRoot(json []byte) int { 13 | sc := 0 14 | spec := getSpecSelector(json) 15 | 16 | jqContainers := gojsonq.New().Reader(bytes.NewReader(json)). 17 | From(spec+".containers"). 18 | Where("securityContext", "!=", nil). 19 | Where("securityContext.runAsNonRoot", "!=", nil). 20 | Where("securityContext.runAsNonRoot", "=", false) 21 | 22 | jqSecurityContext := gojsonq.New().Reader(bytes.NewReader(json)). 23 | From(spec+".securityContext"). 24 | Where("securityContext", "!=", nil). 25 | Where("securityContext.privileged", "!=", nil) 26 | 27 | if strings.Contains(fmt.Sprintf("%v", jqSecurityContext.Get()), "runAsNonRoot:false") { 28 | sc++ 29 | } 30 | 31 | return jqContainers.Count() + sc 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/runAsNonRoot_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | func Test_RunAsNonRoot(t *testing.T) { 9 | var data = ` 10 | --- 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | spec: 14 | template: 15 | spec: 16 | containers: 17 | - name: c1 18 | securityContext: 19 | runAsNonRoot: false 20 | - name: c2 21 | securityContext: 22 | runAsNonRoot: false 23 | - name: c3 24 | securityContext: 25 | runAsNonRoot: false 26 | ` 27 | 28 | json, err := yaml.YAMLToJSON([]byte(data)) 29 | if err != nil { 30 | t.Fatal(err.Error()) 31 | } 32 | 33 | securityContext := RunAsNonRoot(json) 34 | if securityContext != 3 { 35 | t.Errorf("Got %v securityContext wanted %v", securityContext, 3) 36 | } 37 | } 38 | 39 | func Test_RunAsNonRoot_Deploy_Spec(t *testing.T) { 40 | var data = ` 41 | apiVersion: apps/v1 42 | kind: Deployment 43 | metadata: 44 | name: controller-manager 45 | namespace: system 46 | labels: 47 | control-plane: controller-manager 48 | spec: 49 | selector: 50 | matchLabels: 51 | control-plane: controller-manager 52 | replicas: 1 53 | template: 54 | metadata: 55 | annotations: 56 | kubectl.kubernetes.io/default-container: manager 57 | labels: 58 | control-plane: controller-manager 59 | spec: 60 | securityContext: 61 | runAsNonRoot: true 62 | ` 63 | 64 | json, err := yaml.YAMLToJSON([]byte(data)) 65 | if err != nil { 66 | t.Fatal(err.Error()) 67 | } 68 | 69 | securityContext := RunAsNonRoot(json) 70 | if securityContext != 0 { 71 | t.Errorf("Got %v securityContext wanted %v", securityContext, 0) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/rules/runAsUser.go: -------------------------------------------------------------------------------- 1 | // OPR-R8-SC - securityContext set to runAsUser: 0 2 | package rules 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/thedevsaddam/gojsonq/v2" 10 | ) 11 | 12 | func RunAsUser(json []byte) int { 13 | sc := 0 14 | spec := getSpecSelector(json) 15 | 16 | jqContainers := gojsonq.New().Reader(bytes.NewReader(json)). 17 | From(spec+".containers"). 18 | Where("securityContext", "!=", nil). 19 | Where("securityContext.runAsUser", "!=", nil). 20 | Where("securityContext.runAsUser", "=", 0) 21 | 22 | jqSecurityContext := gojsonq.New().Reader(bytes.NewReader(json)). 23 | From(spec+".securityContext"). 24 | Where("securityContext", "!=", nil). 25 | Where("securityContext.privileged", "!=", nil) 26 | 27 | if strings.Contains(fmt.Sprintf("%v", jqSecurityContext.Get()), "runAsUser:0") { 28 | sc++ 29 | } 30 | 31 | return jqContainers.Count() + sc 32 | } 33 | -------------------------------------------------------------------------------- /pkg/rules/runAsUser_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/ghodss/yaml" 5 | "testing" 6 | ) 7 | 8 | func Test_RunAsUser_Zero_Pod(t *testing.T) { 9 | var data = ` 10 | --- 11 | apiVersion: v1 12 | kind: Pod 13 | spec: 14 | containers: 15 | - name: c1 16 | securityContext: 17 | runAsUser: 0 18 | ` 19 | 20 | json, err := yaml.YAMLToJSON([]byte(data)) 21 | if err != nil { 22 | t.Fatal(err.Error()) 23 | } 24 | 25 | securityContext := RunAsUser(json) 26 | if securityContext != 1 { 27 | t.Errorf("Got %v securityContext wanted %v", securityContext, 1) 28 | } 29 | } 30 | 31 | func Test_RunAsUser_Zero_Deploy(t *testing.T) { 32 | var data = ` 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: controller-manager 37 | namespace: system 38 | labels: 39 | control-plane: controller-manager 40 | spec: 41 | selector: 42 | matchLabels: 43 | control-plane: controller-manager 44 | replicas: 1 45 | template: 46 | metadata: 47 | annotations: 48 | kubectl.kubernetes.io/default-container: manager 49 | labels: 50 | control-plane: controller-manager 51 | spec: 52 | securityContext: 53 | runAsUser: 0 54 | ` 55 | 56 | json, err := yaml.YAMLToJSON([]byte(data)) 57 | if err != nil { 58 | t.Fatal(err.Error()) 59 | } 60 | 61 | securityContext := RunAsUser(json) 62 | if securityContext != 1 { 63 | t.Errorf("Got %v securityContext wanted %v", securityContext, 1) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/rules/secretsClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R14-RBAC - ClusterRole has access to Kubernetes secrets 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func SecretsClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("", rule.APIGroups) && 21 | contains("secrets", rule.Resources) && 22 | containsAny([]string{"*", "get", "create", "update", "list", "patch", "watch"}, rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rules/secretsClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_Secrets_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - secrets 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := SecretsClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Incorrect_Secrets_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - authentication.k8s.io 46 | resources: 47 | - secrets 48 | verbs: 49 | - get 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := SecretsClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_Secrets_Verbs_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - "" 73 | resources: 74 | - secrets 75 | verbs: 76 | - get 77 | - list 78 | - create 79 | - update 80 | - delete 81 | - patch 82 | - watch 83 | - deletecollection 84 | ` 85 | json, err := yaml.YAMLToJSON([]byte(data)) 86 | if err != nil { 87 | t.Fatal(err.Error()) 88 | } 89 | 90 | rbac := SecretsClusterRole(json) 91 | if rbac != 1 { 92 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 93 | } 94 | } 95 | 96 | func Test_Secrets_Multiple_Rules(t *testing.T) { 97 | var data = ` 98 | --- 99 | apiVersion: rbac.authorization.k8s.io/v1 100 | kind: ClusterRole 101 | metadata: 102 | name: example-operator 103 | rules: 104 | - apiGroups: 105 | - "" 106 | resources: 107 | - secrets 108 | verbs: 109 | - "*" 110 | - apiGroups: 111 | - "apps" 112 | resources: 113 | - deployments 114 | verbs: 115 | - "*" 116 | ` 117 | json, err := yaml.YAMLToJSON([]byte(data)) 118 | if err != nil { 119 | t.Fatal(err.Error()) 120 | } 121 | 122 | rbac := SecretsClusterRole(json) 123 | if rbac != 1 { 124 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 125 | } 126 | } 127 | 128 | func Test_Secrets_Multiple_API_Groups(t *testing.T) { 129 | var data = ` 130 | --- 131 | apiVersion: rbac.authorization.k8s.io/v1 132 | kind: ClusterRole 133 | metadata: 134 | name: example-operator 135 | rules: 136 | - apiGroups: 137 | - "" 138 | - apps 139 | resources: 140 | - secrets 141 | - deployments 142 | verbs: 143 | - "*" 144 | ` 145 | json, err := yaml.YAMLToJSON([]byte(data)) 146 | if err != nil { 147 | t.Fatal(err.Error()) 148 | } 149 | 150 | rbac := SecretsClusterRole(json) 151 | if rbac != 1 { 152 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /pkg/rules/selector.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/thedevsaddam/gojsonq/v2" 7 | ) 8 | 9 | func getSpecSelector(json []byte) string { 10 | selector := "spec.template.spec" 11 | 12 | jq := gojsonq.New().Reader(bytes.NewReader(json)).From("kind") 13 | if jq.Error() != nil { 14 | return selector 15 | } 16 | 17 | kind := fmt.Sprintf("%s", jq.Get()) 18 | 19 | if kind == "Pod" { 20 | selector = "spec" 21 | } 22 | 23 | return selector 24 | } 25 | -------------------------------------------------------------------------------- /pkg/rules/serviceAccountClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R23-RBAC - ClusterRole has permissions over service account token creation 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func ServiceAccountClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("", rule.APIGroups) && 21 | contains("serviceaccounts/token", rule.Resources) && 22 | containsAny([]string{"*", "create"}, rule.Verbs) { 23 | rbac++ 24 | } 25 | } 26 | 27 | return rbac 28 | 29 | } 30 | -------------------------------------------------------------------------------- /pkg/rules/serviceAccountClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_ServiceAccount_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - serviceaccounts/token 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := ServiceAccountClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_ServiceAccount_Only_Get_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - serviceaccounts/token 48 | verbs: 49 | - get 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := ServiceAccountClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_ServiceAccount_Token_Create_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - "" 73 | resources: 74 | - serviceaccounts/token 75 | verbs: 76 | - create 77 | ` 78 | json, err := yaml.YAMLToJSON([]byte(data)) 79 | if err != nil { 80 | t.Fatal(err.Error()) 81 | } 82 | 83 | rbac := ServiceAccountClusterRole(json) 84 | if rbac != 1 { 85 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 86 | } 87 | } 88 | 89 | func Test_ServiceAccount_Multiple_API_Groups(t *testing.T) { 90 | var data = ` 91 | --- 92 | apiVersion: rbac.authorization.k8s.io/v1 93 | kind: ClusterRole 94 | metadata: 95 | name: example-operator 96 | rules: 97 | - apiGroups: 98 | - "" 99 | - apps 100 | resources: 101 | - serviceaccounts/token 102 | verbs: 103 | - "*" 104 | ` 105 | 106 | json, err := yaml.YAMLToJSON([]byte(data)) 107 | if err != nil { 108 | t.Fatal(err.Error()) 109 | } 110 | 111 | rbac := ServiceAccountClusterRole(json) 112 | if rbac != 1 { 113 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 114 | } 115 | } 116 | 117 | func Test_ServiceAccount_Multiple_Resources(t *testing.T) { 118 | var data = ` 119 | --- 120 | apiVersion: rbac.authorization.k8s.io/v1 121 | kind: ClusterRole 122 | metadata: 123 | name: example-operator 124 | rules: 125 | - apiGroups: 126 | - "" 127 | resources: 128 | - pods 129 | - serviceaccounts/token 130 | verbs: 131 | - "*" 132 | ` 133 | 134 | json, err := yaml.YAMLToJSON([]byte(data)) 135 | if err != nil { 136 | t.Fatal(err.Error()) 137 | } 138 | 139 | rbac := ServiceAccountClusterRole(json) 140 | if rbac != 1 { 141 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 142 | } 143 | } 144 | 145 | func Test_ServiceAccount_Multiple_Rules(t *testing.T) { 146 | var data = ` 147 | --- 148 | apiVersion: rbac.authorization.k8s.io/v1 149 | kind: ClusterRole 150 | metadata: 151 | name: example-operator 152 | rules: 153 | - apiGroups: 154 | - "" 155 | resources: 156 | - serviceaccounts 157 | verbs: 158 | - "*" 159 | - apiGroups: 160 | - "apps" 161 | resources: 162 | - deployments 163 | verbs: 164 | - "*" 165 | - apiGroups: 166 | - "" 167 | resources: 168 | - serviceaccounts/token 169 | verbs: 170 | - create 171 | ` 172 | 173 | json, err := yaml.YAMLToJSON([]byte(data)) 174 | if err != nil { 175 | t.Fatal(err.Error()) 176 | } 177 | 178 | rbac := ServiceAccountClusterRole(json) 179 | if rbac != 1 { 180 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 181 | } 182 | } 183 | 184 | func Test_Old_API_Version(t *testing.T) { 185 | var data = ` 186 | --- 187 | apiVersion: rbac.authorization.k8s.io/v1beta1 188 | kind: ClusterRole 189 | metadata: 190 | name: example-operator 191 | rules: 192 | - apiGroups: 193 | - "" 194 | resources: 195 | - serviceaccounts/token 196 | verbs: 197 | - "*" 198 | ` 199 | 200 | json, err := yaml.YAMLToJSON([]byte(data)) 201 | if err != nil { 202 | t.Fatal(err.Error()) 203 | } 204 | 205 | rbac := ServiceAccountClusterRole(json) 206 | if rbac != 1 { 207 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /pkg/rules/slice.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | func contains(needle string, haystack []string) bool { 4 | for _, item := range haystack { 5 | if item == needle { 6 | return true 7 | } 8 | } 9 | return false 10 | } 11 | 12 | func containsAny(needles []string, haystack []string) bool { 13 | for _, item := range haystack { 14 | for _, needle := range needles { 15 | if item == needle { 16 | return true 17 | } 18 | } 19 | } 20 | 21 | return false 22 | } 23 | 24 | func containsAll(needles []string, haystack []string) bool { 25 | OUTER: 26 | for _, needle := range needles { 27 | for _, item := range haystack { 28 | if needle == item { 29 | continue OUTER 30 | } 31 | } 32 | return false 33 | } 34 | return true 35 | } 36 | -------------------------------------------------------------------------------- /pkg/rules/starAllClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R11-RBAC - ClusterRole has full permissions over all resources 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func StarAllClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("*", rule.APIGroups) && 21 | contains("*", rule.Resources) && 22 | contains("*", rule.Verbs) { 23 | rbac++ 24 | } else if contains("*", rule.APIGroups) && 25 | contains("*", rule.Resources) && 26 | containsAll([]string{ 27 | "get", 28 | "create", 29 | "update", 30 | "list", 31 | "patch", 32 | "watch", 33 | "delete", 34 | "deletecollection", 35 | }, rule.Verbs) { 36 | rbac++ 37 | } 38 | } 39 | 40 | return rbac 41 | } 42 | -------------------------------------------------------------------------------- /pkg/rules/starAllClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_All_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "*" 19 | resources: 20 | - "*" 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := StarAllClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_Limited_APIgroups(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - networking.k8s.io 46 | resources: 47 | - "*" 48 | verbs: 49 | - "*" 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := StarAllClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_All_Verbs_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - "*" 73 | resources: 74 | - "*" 75 | verbs: 76 | - get 77 | - create 78 | - list 79 | - patch 80 | - update 81 | - watch 82 | - delete 83 | - deletecollection 84 | ` 85 | json, err := yaml.YAMLToJSON([]byte(data)) 86 | if err != nil { 87 | t.Fatal(err.Error()) 88 | } 89 | 90 | rbac := StarAllClusterRole(json) 91 | if rbac != 1 { 92 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 93 | } 94 | } 95 | func Test_Most_Verbs_Permissions(t *testing.T) { 96 | var data = ` 97 | --- 98 | apiVersion: rbac.authorization.k8s.io/v1 99 | kind: ClusterRole 100 | metadata: 101 | name: example-operator 102 | rules: 103 | - apiGroups: 104 | - "*" 105 | resources: 106 | - "*" 107 | verbs: 108 | - get 109 | - create 110 | - list 111 | - patch 112 | - update 113 | - watch 114 | ` 115 | json, err := yaml.YAMLToJSON([]byte(data)) 116 | if err != nil { 117 | t.Fatal(err.Error()) 118 | } 119 | 120 | rbac := StarAllClusterRole(json) 121 | if rbac != 0 { 122 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 123 | } 124 | } 125 | 126 | func Test_Multiple_API_Groups(t *testing.T) { 127 | var data = ` 128 | --- 129 | apiVersion: rbac.authorization.k8s.io/v1 130 | kind: ClusterRole 131 | metadata: 132 | name: example-operator 133 | rules: 134 | - apiGroups: 135 | - "*" 136 | - "apps" 137 | resources: 138 | - "*" 139 | verbs: 140 | - "*" 141 | ` 142 | 143 | json, err := yaml.YAMLToJSON([]byte(data)) 144 | if err != nil { 145 | t.Fatal(err.Error()) 146 | } 147 | 148 | rbac := StarAllClusterRole(json) 149 | if rbac != 1 { 150 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 151 | } 152 | } 153 | 154 | func Test_Multiple_Resources(t *testing.T) { 155 | var data = ` 156 | --- 157 | apiVersion: rbac.authorization.k8s.io/v1 158 | kind: ClusterRole 159 | metadata: 160 | name: example-operator 161 | rules: 162 | - apiGroups: 163 | - "*" 164 | resources: 165 | - "*" 166 | - "pods" 167 | verbs: 168 | - "*" 169 | ` 170 | 171 | json, err := yaml.YAMLToJSON([]byte(data)) 172 | if err != nil { 173 | t.Fatal(err.Error()) 174 | } 175 | 176 | rbac := StarAllClusterRole(json) 177 | if rbac != 1 { 178 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 179 | } 180 | } 181 | 182 | func Test_multiple_rules(t *testing.T) { 183 | var data = ` 184 | --- 185 | apiVersion: rbac.authorization.k8s.io/v1 186 | kind: ClusterRole 187 | metadata: 188 | name: example-operator 189 | rules: 190 | - apiGroups: 191 | - "*" 192 | resources: 193 | - "*" 194 | - "pods" 195 | verbs: 196 | - "*" 197 | - apiGroups: 198 | - "" 199 | resources: 200 | - "pods" 201 | verbs: 202 | - "get" 203 | ` 204 | 205 | json, err := yaml.YAMLToJSON([]byte(data)) 206 | if err != nil { 207 | t.Fatal(err.Error()) 208 | } 209 | 210 | rbac := StarAllClusterRole(json) 211 | if rbac != 1 { 212 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /pkg/rules/starAllCoreAPIClusterRole.go: -------------------------------------------------------------------------------- 1 | // OPR-R12-RBAC - ClusterRole has full permissions over all CoreAPI resources 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func StarAllCoreAPIClusterRole(input []byte) int { 11 | rbac := 0 12 | 13 | clusterRole := &rbacv1.ClusterRole{} 14 | err := json.Unmarshal(input, clusterRole) 15 | if err != nil { 16 | return 0 17 | } 18 | 19 | for _, rule := range clusterRole.Rules { 20 | if contains("", rule.APIGroups) && 21 | contains("*", rule.Resources) && 22 | contains("*", rule.Verbs) { 23 | rbac++ 24 | } else if contains("", rule.APIGroups) && 25 | contains("*", rule.Resources) && 26 | containsAll([]string{ 27 | "get", 28 | "create", 29 | "update", 30 | "list", 31 | "patch", 32 | "watch", 33 | "delete", 34 | "deletecollection", 35 | }, rule.Verbs) { 36 | rbac++ 37 | } 38 | } 39 | 40 | return rbac 41 | } 42 | -------------------------------------------------------------------------------- /pkg/rules/starAllCoreAPIClusterRole_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_CoreAPI_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - "*" 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := StarAllCoreAPIClusterRole(json) 31 | if rbac != 1 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 33 | } 34 | } 35 | 36 | func Test_CoreAPI_Limited_Resources(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - pods 48 | verbs: 49 | - get 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := StarAllCoreAPIClusterRole(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_CoreAPI_Verbs_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - "" 73 | resources: 74 | - "*" 75 | verbs: 76 | - get 77 | - create 78 | - list 79 | - patch 80 | - update 81 | - watch 82 | - delete 83 | - deletecollection 84 | ` 85 | json, err := yaml.YAMLToJSON([]byte(data)) 86 | if err != nil { 87 | t.Fatal(err.Error()) 88 | } 89 | 90 | rbac := StarAllCoreAPIClusterRole(json) 91 | if rbac != 1 { 92 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 93 | } 94 | } 95 | 96 | func Test_CoreAPI_Multiple_Rules(t *testing.T) { 97 | var data = ` 98 | --- 99 | apiVersion: rbac.authorization.k8s.io/v1 100 | kind: ClusterRole 101 | metadata: 102 | name: example-operator 103 | rules: 104 | - apiGroups: 105 | - "" 106 | resources: 107 | - "*" 108 | verbs: 109 | - get 110 | - create 111 | - list 112 | - patch 113 | - update 114 | - watch 115 | - delete 116 | - deletecollection 117 | - apiGroups: 118 | - "apps" 119 | resources: 120 | - "deployment" 121 | verbs: 122 | - get 123 | - create 124 | - list 125 | - patch 126 | - update 127 | - watch 128 | - delete 129 | - deletecollection 130 | ` 131 | json, err := yaml.YAMLToJSON([]byte(data)) 132 | if err != nil { 133 | t.Fatal(err.Error()) 134 | } 135 | 136 | rbac := StarAllCoreAPIClusterRole(json) 137 | if rbac != 1 { 138 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 139 | } 140 | } 141 | 142 | func Test_CoreAPI_Some_Verbs(t *testing.T) { 143 | var data = ` 144 | --- 145 | apiVersion: rbac.authorization.k8s.io/v1 146 | kind: ClusterRole 147 | metadata: 148 | name: example-operator 149 | rules: 150 | - apiGroups: 151 | - "" 152 | resources: 153 | - "*" 154 | verbs: 155 | - get 156 | - create 157 | - list 158 | - patch 159 | - update 160 | - watch 161 | ` 162 | json, err := yaml.YAMLToJSON([]byte(data)) 163 | if err != nil { 164 | t.Fatal(err.Error()) 165 | } 166 | 167 | rbac := StarAllCoreAPIClusterRole(json) 168 | if rbac != 0 { 169 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /pkg/rules/starClusterRoleAndBindings.go: -------------------------------------------------------------------------------- 1 | // OPR-R13-RBAC - ClusterRole has full permissions over ClusterRoles and ClusterRoleBindings 2 | package rules 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | ) 9 | 10 | func StarClusterRoleAndBindings(input []byte) int { 11 | rbac := 0 12 | var foundCR, foundCRB bool 13 | 14 | clusterRole := &rbacv1.ClusterRole{} 15 | err := json.Unmarshal(input, clusterRole) 16 | if err != nil { 17 | return 0 18 | } 19 | 20 | for _, rule := range clusterRole.Rules { 21 | if contains("rbac.authorization.k8s.io", rule.APIGroups) && 22 | containsAll([]string{"clusterroles", "clusterrolebindings"}, rule.Resources) && 23 | (contains("*", rule.Verbs) || containsAll([]string{ 24 | "get", 25 | "create", 26 | "update", 27 | "list", 28 | "patch", 29 | "watch", 30 | "delete", 31 | "deletecollection", 32 | }, rule.Verbs)) { 33 | rbac++ 34 | } else if contains("rbac.authorization.k8s.io", rule.APIGroups) && 35 | contains("clusterroles", rule.Resources) && 36 | (contains("*", rule.Verbs) || containsAll([]string{ 37 | "get", 38 | "create", 39 | "update", 40 | "list", 41 | "patch", 42 | "watch", 43 | "delete", 44 | "deletecollection", 45 | }, rule.Verbs)) { 46 | foundCR = true 47 | if foundCR && foundCRB { 48 | rbac++ 49 | } 50 | } else if contains("rbac.authorization.k8s.io", rule.APIGroups) && 51 | contains("clusterrolebindings", rule.Resources) && 52 | (contains("*", rule.Verbs) || containsAll([]string{ 53 | "get", 54 | "create", 55 | "update", 56 | "list", 57 | "patch", 58 | "watch", 59 | "delete", 60 | "deletecollection", 61 | }, rule.Verbs)) { 62 | foundCRB = true 63 | if foundCR && foundCRB { 64 | rbac++ 65 | } 66 | } 67 | } 68 | 69 | return rbac 70 | } 71 | -------------------------------------------------------------------------------- /pkg/rules/starClusterRoleAndBindings_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | func Test_ClusterRoles_Permissions(t *testing.T) { 10 | var data = ` 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: example-operator 16 | rules: 17 | - apiGroups: 18 | - rbac.authorization.k8s.io 19 | resources: 20 | - clusterroles 21 | verbs: 22 | - "*" 23 | ` 24 | 25 | json, err := yaml.YAMLToJSON([]byte(data)) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | 30 | rbac := StarClusterRoleAndBindings(json) 31 | if rbac != 0 { 32 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 33 | } 34 | } 35 | 36 | func Test_ClusterRoleBindings_Permissions(t *testing.T) { 37 | var data = ` 38 | --- 39 | apiVersion: rbac.authorization.k8s.io/v1 40 | kind: ClusterRole 41 | metadata: 42 | name: example-operator 43 | rules: 44 | - apiGroups: 45 | - rbac.authorization.k8s.io 46 | resources: 47 | - clusterrolebindings 48 | verbs: 49 | - "*" 50 | ` 51 | 52 | json, err := yaml.YAMLToJSON([]byte(data)) 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | rbac := StarClusterRoleAndBindings(json) 58 | if rbac != 0 { 59 | t.Errorf("Got %v permissions wanted %v", rbac, 0) 60 | } 61 | } 62 | 63 | func Test_Both_Permissions(t *testing.T) { 64 | var data = ` 65 | --- 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | kind: ClusterRole 68 | metadata: 69 | name: example-operator 70 | rules: 71 | - apiGroups: 72 | - rbac.authorization.k8s.io 73 | resources: 74 | - clusterrolebindings 75 | - clusterroles 76 | verbs: 77 | - get 78 | - create 79 | - list 80 | - patch 81 | - update 82 | - watch 83 | - delete 84 | - deletecollection 85 | ` 86 | json, err := yaml.YAMLToJSON([]byte(data)) 87 | if err != nil { 88 | t.Fatal(err.Error()) 89 | } 90 | 91 | rbac := StarClusterRoleAndBindings(json) 92 | if rbac != 1 { 93 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 94 | } 95 | } 96 | 97 | func Test_ClusterRoles_Multiple_Rules(t *testing.T) { 98 | var data = ` 99 | --- 100 | apiVersion: rbac.authorization.k8s.io/v1 101 | kind: ClusterRole 102 | metadata: 103 | name: example-operator 104 | rules: 105 | - apiGroups: 106 | - rbac.authorization.k8s.io 107 | resources: 108 | - clusterrolebindings 109 | - clusterroles 110 | verbs: 111 | - get 112 | - create 113 | - list 114 | - patch 115 | - update 116 | - watch 117 | - delete 118 | - deletecollection 119 | - apiGroups: 120 | - apps 121 | resources: 122 | - deployment 123 | verbs: 124 | - get 125 | - create 126 | - list 127 | - patch 128 | - update 129 | - watch 130 | - delete 131 | - deletecollection 132 | ` 133 | json, err := yaml.YAMLToJSON([]byte(data)) 134 | if err != nil { 135 | t.Fatal(err.Error()) 136 | } 137 | 138 | rbac := StarClusterRoleAndBindings(json) 139 | if rbac != 1 { 140 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 141 | } 142 | } 143 | 144 | func Test_ClusterRole_Extra_Resources(t *testing.T) { 145 | var data = ` 146 | --- 147 | apiVersion: rbac.authorization.k8s.io/v1 148 | kind: ClusterRole 149 | metadata: 150 | name: example-operator 151 | rules: 152 | - apiGroups: 153 | - rbac.authorization.k8s.io 154 | - apps 155 | resources: 156 | - clusterrolebindings 157 | - clusterroles 158 | - deployments 159 | verbs: 160 | - get 161 | - create 162 | - list 163 | - patch 164 | - update 165 | - watch 166 | - delete 167 | - deletecollection 168 | ` 169 | json, err := yaml.YAMLToJSON([]byte(data)) 170 | if err != nil { 171 | t.Fatal(err.Error()) 172 | } 173 | 174 | rbac := StarClusterRoleAndBindings(json) 175 | if rbac != 1 { 176 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 177 | } 178 | } 179 | 180 | func Test_ClusterRole_Split_Rules(t *testing.T) { 181 | var data = ` 182 | --- 183 | apiVersion: rbac.authorization.k8s.io/v1 184 | kind: ClusterRole 185 | metadata: 186 | name: example-operator 187 | rules: 188 | - apiGroups: 189 | - rbac.authorization.k8s.io 190 | resources: 191 | - clusterrolebindings 192 | verbs: 193 | - "*" 194 | - apiGroups: 195 | - rbac.authorization.k8s.io 196 | resources: 197 | - clusterroles 198 | verbs: 199 | - "*" 200 | ` 201 | json, err := yaml.YAMLToJSON([]byte(data)) 202 | if err != nil { 203 | t.Fatal(err.Error()) 204 | } 205 | 206 | rbac := StarClusterRoleAndBindings(json) 207 | if rbac != 1 { 208 | t.Errorf("Got %v permissions wanted %v", rbac, 1) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /test/0_test_deps.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | load '_helper' 4 | 5 | setup() { 6 | _global_setup 7 | } 8 | 9 | teardown() { 10 | _global_teardown 11 | } 12 | 13 | @test "test dep - jq is installed" { 14 | run command -v jq 15 | 16 | assert_success 17 | } 18 | -------------------------------------------------------------------------------- /test/2_regression.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | load '_helper' 4 | 5 | setup() { 6 | _global_setup 7 | } 8 | 9 | teardown() { 10 | _global_teardown 11 | } 12 | 13 | # --- 14 | 15 | @test "errors with no filename" { 16 | run _app 17 | assert_failure 18 | } 19 | 20 | @test "errors with no filename - output logs (local)" { 21 | run "${BIN_DIR:-}"/badrobot scan 22 | assert_failure 23 | assert_line "Error: file path is required" 24 | } 25 | 26 | @test "errors with invalid file" { 27 | run _app somefile.yaml 28 | assert_failure 29 | assert_file_not_found 30 | } 31 | 32 | @test "errors with empty file" { 33 | run _app "${TEST_DIR}/asset/reg-empty-file" 34 | assert_failure 35 | assert_invalid_input 36 | } 37 | 38 | @test "errors with empty file (json)" { 39 | run _app "${TEST_DIR}/asset/reg-empty-file" --json 40 | assert_invalid_input 41 | assert_failure 42 | } 43 | 44 | @test "errors with empty JSON (json)" { 45 | run _app "${TEST_DIR}/asset/reg-empty-json-file" --json 46 | assert_invalid_input 47 | assert_failure 48 | } 49 | 50 | # TBD KGW - kubekind no longer working for schema validation, needs replacing 51 | # @test "errors with invalid kind" { 52 | # run _app "${TEST_DIR}/asset/reg-invalid-kind.yaml" 53 | # assert_invalid_input 54 | # assert_failure 55 | # } 56 | 57 | # TBD KGW - kubekind no longer working for schema validation, needs replacing 58 | # @test "errors with invalid schema" { 59 | # run _app "${TEST_DIR}/asset/reg-invalid-schema.yaml" 60 | # assert_invalid_input 61 | # assert_failure 62 | # } 63 | -------------------------------------------------------------------------------- /test/_helper.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | load './bin/bats-support/load' 4 | load './bin/bats-assert/load' 5 | 6 | export TEST_DIR="." 7 | 8 | export BIN_DIR='../dist/' 9 | 10 | _global_setup() { 11 | [ ! -f "${BATS_PARENT_TMPNAME}".skip ] || skip "skip remaining tests" 12 | } 13 | 14 | _global_teardown() { 15 | if [ -z "$BATS_TEST_COMPLETED" ]; then 16 | touch "${BATS_PARENT_TMPNAME}".skip 17 | fi 18 | } 19 | 20 | _test_description_matches_regex() { 21 | [[ "${BATS_TEST_DESCRIPTION}" =~ ${1} ]] 22 | } 23 | 24 | _app() { 25 | local ARGS="${@:-}" 26 | if [[ "${BIN_DIR}" != "" ]]; then 27 | # remove --json flags 28 | ARGS=$(echo "${ARGS}" | sed -E 's,--json,,g') 29 | fi 30 | "${BIN_DIR}"/badrobot scan "${ARGS}"; 31 | } 32 | 33 | assert_zero_points() { 34 | assert_output --regexp ".*with a score of 0 points.*" 35 | assert_failure 36 | } 37 | 38 | assert_lt_zero_points() { 39 | assert_output --regexp ".*\with a score of \-[1-9][0-9]* points.*" 40 | assert_failure 41 | } 42 | 43 | assert_file_not_found() { 44 | assert_output --regexp ".*File somefile.yaml does not exist.*" \ 45 | || assert_output --regexp ".*no such file or directory.*" \ 46 | || assert_output --regexp ".*Invalid input.*" 47 | } 48 | 49 | assert_invalid_input() { 50 | assert_output --regexp ' "message": "Invalid input"' \ 51 | || assert_output --regexp ".*Kubernetes kind not found.*" \ 52 | || assert_output --regexp ".*no such file or directory.*" \ 53 | || assert_output --regexp ".*Invalid input.*" 54 | } 55 | -------------------------------------------------------------------------------- /test/asset/cr-ad-both-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - admissionregistration.k8s.io 8 | resources: 9 | - mutatingwebhookconfigurations 10 | - validatingwebhookconfigurations 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-ad-both-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - admissionregistration.k8s.io 8 | resources: 9 | - mutatingwebhookconfigurations 10 | - validatingwebhookconfigurations 11 | verbs: 12 | - create 13 | - update 14 | - delete 15 | - patch 16 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-ad-mutating-webhook-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - admissionregistration.k8s.io 8 | resources: 9 | - mutatingwebhookconfigurations 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-ad-mutating-webhook-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - admissionregistration.k8s.io 8 | resources: 9 | - mutatingwebhookconfigurations 10 | verbs: 11 | - create 12 | - update 13 | - delete 14 | - patch 15 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-ad-validating-webhook-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - admissionregistration.k8s.io 8 | resources: 9 | - validatingwebhookconfigurations 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-ad-validating-webhook-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - admissionregistration.k8s.io 8 | resources: 9 | - validatingwebhookconfigurations 10 | verbs: 11 | - create 12 | - update 13 | - delete 14 | - patch 15 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-all-clusterrolebindings-only.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - rbac.authorization.k8s.io 8 | resources: 9 | - clusterrolebindings 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-all-clusterroles-only.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - rbac.authorization.k8s.io 8 | resources: 9 | - clusterroles 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-all-clusterrolesandbindings-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - rbac.authorization.k8s.io 8 | resources: 9 | - clusterrolebindings 10 | - clusterroles 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-all-clusterrolesandbindings-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - rbac.authorization.k8s.io 8 | resources: 9 | - clusterrolebindings 10 | - clusterroles 11 | verbs: 12 | - get 13 | - create 14 | - list 15 | - patch 16 | - update 17 | - watch 18 | - delete 19 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-all-crbs-separate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - rbac.authorization.k8s.io 8 | resources: 9 | - clusterroles 10 | verbs: 11 | - "*" 12 | - apiGroups: 13 | - rbac.authorization.k8s.io 14 | resources: 15 | - clusterrolebindings 16 | verbs: 17 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-all-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "*" 8 | resources: 9 | - "*" 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-all-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "*" 8 | resources: 9 | - "*" 10 | verbs: 11 | - get 12 | - create 13 | - list 14 | - patch 15 | - update 16 | - watch 17 | - delete 18 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-bind.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - rbac.authorization.k8s.io 8 | resources: 9 | - clusterroles 10 | verbs: 11 | - bind -------------------------------------------------------------------------------- /test/asset/cr-coreapi-all-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - "*" 10 | verbs: 11 | - get 12 | - create 13 | - list 14 | - patch 15 | - update 16 | - watch 17 | - delete 18 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-coreapi-limited-resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - configmaps 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-coreapi-limited-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - "*" 10 | verbs: 11 | - get -------------------------------------------------------------------------------- /test/asset/cr-coreapi-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - "*" 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-custom-resource-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - apiextensions.k8s.io 8 | resources: 9 | - customresourcedefinitions 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-custom-resource-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - apiextensions.k8s.io 8 | resources: 9 | - customresourcedefinitions 10 | verbs: 11 | - create 12 | - patch 13 | - modify 14 | - delete 15 | - deletecollection 16 | -------------------------------------------------------------------------------- /test/asset/cr-escalate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - rbac.authorization.k8s.io 8 | resources: 9 | - clusterroles 10 | verbs: 11 | - escalate -------------------------------------------------------------------------------- /test/asset/cr-impersonate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - serviceaccounts 10 | verbs: 11 | - impersonate -------------------------------------------------------------------------------- /test/asset/cr-network-get.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "networking.k8s.io" 8 | resources: 9 | - "*" 10 | verbs: 11 | - get -------------------------------------------------------------------------------- /test/asset/cr-network-policy-get.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "networking.k8s.io" 8 | resources: 9 | - "networkpolicy" 10 | verbs: 11 | - get -------------------------------------------------------------------------------- /test/asset/cr-network-policy-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "networking.k8s.io" 8 | resources: 9 | - "networkpolicy" 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-network-policy-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "networking.k8s.io" 8 | resources: 9 | - "networkpolicy" 10 | verbs: 11 | - create 12 | - update 13 | - delete 14 | - patch 15 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-network-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "networking.k8s.io" 8 | resources: 9 | - "*" 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-network-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "networking.k8s.io" 8 | resources: 9 | - "*" 10 | verbs: 11 | - create 12 | - update 13 | - delete 14 | - patch 15 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-node-proxy-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - nodes/proxy 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-node-proxy-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - nodes/proxy 10 | verbs: 11 | - get 12 | - create -------------------------------------------------------------------------------- /test/asset/cr-node-proxy-watch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - nodes/proxy 10 | verbs: 11 | - watch -------------------------------------------------------------------------------- /test/asset/cr-node-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - nodes 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-node-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - nodes 10 | verbs: 11 | - get 12 | - create -------------------------------------------------------------------------------- /test/asset/cr-node-watch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - nodes 10 | verbs: 11 | - watch -------------------------------------------------------------------------------- /test/asset/cr-noncoreapi-limited.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - app 8 | resources: 9 | - pods 10 | verbs: 11 | - update -------------------------------------------------------------------------------- /test/asset/cr-noncoreapi-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - apps 8 | resources: 9 | - "*" 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-pods-podsexec-combined-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | - pods/exec 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-pods-podsexec-combined-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | - pods/exec 11 | verbs: 12 | - get 13 | - create -------------------------------------------------------------------------------- /test/asset/cr-pods-podsexec-exact-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - get 12 | - apiGroups: 13 | - "" 14 | resources: 15 | - pods/exec 16 | verbs: 17 | - create -------------------------------------------------------------------------------- /test/asset/cr-pods-podsexec-incorrect-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - delete 12 | - apiGroups: 13 | - "" 14 | resources: 15 | - pods/exec 16 | verbs: 17 | - create -------------------------------------------------------------------------------- /test/asset/cr-pods-podsexec-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - "*" 12 | - apiGroups: 13 | - "" 14 | resources: 15 | - pods/exec 16 | verbs: 17 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-pods-podsexec-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - delete 12 | - deletecollection 13 | - create 14 | - patch 15 | - get 16 | - list 17 | - update 18 | - watch 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - pods/exec 23 | verbs: 24 | - delete 25 | - deletecollection 26 | - create 27 | - patch 28 | - get 29 | - list 30 | - update 31 | - watch -------------------------------------------------------------------------------- /test/asset/cr-pods-star-podsexec-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - "*" 12 | - apiGroups: 13 | - "" 14 | resources: 15 | - pods/exec 16 | verbs: 17 | - delete 18 | - deletecollection 19 | - create 20 | - patch 21 | - get 22 | - list 23 | - update 24 | - watch -------------------------------------------------------------------------------- /test/asset/cr-pods-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-pods-verbs-podsexec-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - delete 12 | - deletecollection 13 | - create 14 | - patch 15 | - get 16 | - list 17 | - update 18 | - watch 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - pods/exec 23 | verbs: 24 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-pods-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods 10 | verbs: 11 | - get 12 | - create -------------------------------------------------------------------------------- /test/asset/cr-podsexec-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods/exec 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-podsexec-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods/exec 10 | verbs: 11 | - get 12 | - create -------------------------------------------------------------------------------- /test/asset/cr-podslog-single-verb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods/log 10 | verbs: 11 | - update 12 | -------------------------------------------------------------------------------- /test/asset/cr-podslog-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods/log 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-podslog-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - pods/log 10 | verbs: 11 | - create 12 | - delete 13 | - deletecollection 14 | - patch 15 | - update -------------------------------------------------------------------------------- /test/asset/cr-pvc-separate-resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - persistentvolumes 10 | verbs: 11 | - delete 12 | - deletecollection 13 | - create 14 | - patch 15 | - get 16 | - list 17 | - update 18 | - watch 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - persistentvolumeclaims 23 | verbs: 24 | - delete 25 | - deletecollection 26 | - create 27 | - patch 28 | - get 29 | - list 30 | - update 31 | - watch -------------------------------------------------------------------------------- /test/asset/cr-pvc-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - persistentvolumes 10 | - persistentvolumeclaims 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-pvc-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - persistentvolumes 10 | - persistentvolumeclaims 11 | verbs: 12 | - delete 13 | - deletecollection 14 | - create 15 | - patch 16 | - get 17 | - list 18 | - update 19 | - watch -------------------------------------------------------------------------------- /test/asset/cr-remove-events-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - events 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-remove-events-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - events 10 | verbs: 11 | - delete 12 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-sa-only-get.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - serviceaccounts 10 | verbs: 11 | - get -------------------------------------------------------------------------------- /test/asset/cr-sa-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - serviceaccounts 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-sa-token-only-get.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - serviceaccounts/token 10 | verbs: 11 | - get -------------------------------------------------------------------------------- /test/asset/cr-sa-token-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - serviceaccounts/token 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/cr-sa-token-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - serviceaccounts/token 10 | verbs: 11 | - create -------------------------------------------------------------------------------- /test/asset/cr-sa-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - serviceaccounts 10 | verbs: 11 | - create -------------------------------------------------------------------------------- /test/asset/cr-secrets-all-verbs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - secrets 10 | verbs: 11 | - get 12 | - list 13 | - create 14 | - update 15 | - delete 16 | - patch 17 | - watch 18 | - deletecollection -------------------------------------------------------------------------------- /test/asset/cr-secrets-star.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-operator 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - secrets 10 | verbs: 11 | - "*" -------------------------------------------------------------------------------- /test/asset/crb-cluster-admin-suffix.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: manager-rolebinding 5 | subjects: 6 | - kind: ServiceAccount 7 | name: manager-rolebinding 8 | # Replace this with the namespace the operator is deployed in. 9 | namespace: system 10 | roleRef: 11 | kind: ClusterRole 12 | name: cluster-admin-suffix 13 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /test/asset/crb-cluster-admin.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: manager-rolebinding 5 | subjects: 6 | - kind: ServiceAccount 7 | name: manager-rolebinding 8 | # Replace this with the namespace the operator is deployed in. 9 | namespace: system 10 | roleRef: 11 | kind: ClusterRole 12 | name: cluster-admin 13 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /test/asset/crb-dedicated-cr.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: manager-rolebinding 5 | subjects: 6 | - kind: ServiceAccount 7 | name: manager-rolebinding 8 | # Replace this with the namespace the operator is deployed in. 9 | namespace: system 10 | roleRef: 11 | kind: ClusterRole 12 | name: dedicated-operator 13 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /test/asset/crb-ns-dedicated.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /test/asset/crb-ns-default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: default 13 | -------------------------------------------------------------------------------- /test/asset/crb-ns-kubesystem.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: kube-system 13 | -------------------------------------------------------------------------------- /test/asset/crb-prefix-cluster-admin.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: manager-rolebinding 5 | subjects: 6 | - kind: ServiceAccount 7 | name: manager-rolebinding 8 | # Replace this with the namespace the operator is deployed in. 9 | namespace: system 10 | roleRef: 11 | kind: ClusterRole 12 | name: prefix-cluster-admin 13 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /test/asset/deploy-ns-dedicated.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | runAsNonRoot: true 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | livenessProbe: 32 | httpGet: 33 | path: /healthz 34 | port: 8081 35 | initialDelaySeconds: 15 36 | periodSeconds: 20 37 | readinessProbe: 38 | httpGet: 39 | path: /readyz 40 | port: 8081 41 | initialDelaySeconds: 5 42 | periodSeconds: 10 43 | # TODO(user): Configure the resources accordingly based on the project requirements. 44 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 45 | resources: 46 | limits: 47 | cpu: 500m 48 | memory: 128Mi 49 | requests: 50 | cpu: 10m 51 | memory: 64Mi 52 | serviceAccountName: controller-manager 53 | terminationGracePeriodSeconds: 10 54 | -------------------------------------------------------------------------------- /test/asset/deploy-ns-default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: default 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | runAsNonRoot: true 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | livenessProbe: 32 | httpGet: 33 | path: /healthz 34 | port: 8081 35 | initialDelaySeconds: 15 36 | periodSeconds: 20 37 | readinessProbe: 38 | httpGet: 39 | path: /readyz 40 | port: 8081 41 | initialDelaySeconds: 5 42 | periodSeconds: 10 43 | # TODO(user): Configure the resources accordingly based on the project requirements. 44 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 45 | resources: 46 | limits: 47 | cpu: 500m 48 | memory: 128Mi 49 | requests: 50 | cpu: 10m 51 | memory: 64Mi 52 | serviceAccountName: controller-manager 53 | terminationGracePeriodSeconds: 10 54 | -------------------------------------------------------------------------------- /test/asset/deploy-ns-kubesystem.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: kube-system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | runAsNonRoot: true 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | livenessProbe: 32 | httpGet: 33 | path: /healthz 34 | port: 8081 35 | initialDelaySeconds: 15 36 | periodSeconds: 20 37 | readinessProbe: 38 | httpGet: 39 | path: /readyz 40 | port: 8081 41 | initialDelaySeconds: 5 42 | periodSeconds: 10 43 | # TODO(user): Configure the resources accordingly based on the project requirements. 44 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 45 | resources: 46 | limits: 47 | cpu: 500m 48 | memory: 128Mi 49 | requests: 50 | cpu: 10m 51 | memory: 64Mi 52 | serviceAccountName: controller-manager 53 | terminationGracePeriodSeconds: 10 54 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-both-all.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | allowPrivilegeEscalation: false 22 | privileged: false 23 | readOnlyRootFilesystem: true 24 | runAsNonRoot: true 25 | runAsGroup: 25000 26 | runAsUser: 20000 27 | capabilities: 28 | drop: 29 | - ALL 30 | containers: 31 | - command: 32 | - /manager 33 | args: 34 | - --leader-elect 35 | image: controller:latest 36 | name: manager 37 | securityContext: 38 | allowPrivilegeEscalation: false 39 | privileged: false 40 | readOnlyRootFilesystem: true 41 | runAsNonRoot: true 42 | runAsGroup: 25000 43 | runAsUser: 20000 44 | capabilities: 45 | drop: 46 | - ALL 47 | livenessProbe: 48 | httpGet: 49 | path: /healthz 50 | port: 8081 51 | initialDelaySeconds: 15 52 | periodSeconds: 20 53 | readinessProbe: 54 | httpGet: 55 | path: /readyz 56 | port: 8081 57 | initialDelaySeconds: 5 58 | periodSeconds: 10 59 | # TODO(user): Configure the resources accordingly based on the project requirements. 60 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 61 | resources: 62 | limits: 63 | cpu: 500m 64 | memory: 128Mi 65 | requests: 66 | cpu: 10m 67 | memory: 64Mi 68 | serviceAccountName: controller-manager 69 | terminationGracePeriodSeconds: 10 70 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-containers-all.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | allowPrivilegeEscalation: false 29 | privileged: false 30 | readOnlyRootFilesystem: true 31 | runAsNonRoot: true 32 | runAsGroup: 25000 33 | runAsUser: 20000 34 | capabilities: 35 | drop: 36 | - ALL 37 | livenessProbe: 38 | httpGet: 39 | path: /healthz 40 | port: 8081 41 | initialDelaySeconds: 15 42 | periodSeconds: 20 43 | readinessProbe: 44 | httpGet: 45 | path: /readyz 46 | port: 8081 47 | initialDelaySeconds: 5 48 | periodSeconds: 10 49 | # TODO(user): Configure the resources accordingly based on the project requirements. 50 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 51 | resources: 52 | limits: 53 | cpu: 500m 54 | memory: 128Mi 55 | requests: 56 | cpu: 10m 57 | memory: 64Mi 58 | serviceAccountName: controller-manager 59 | terminationGracePeriodSeconds: 10 60 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-containers-allow-priv.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | allowPrivilegeEscalation: true 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-containers-cap-sysadmin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | capabilities: 29 | add: 30 | - SYS_ADMIN 31 | livenessProbe: 32 | httpGet: 33 | path: /healthz 34 | port: 8081 35 | initialDelaySeconds: 15 36 | periodSeconds: 20 37 | readinessProbe: 38 | httpGet: 39 | path: /readyz 40 | port: 8081 41 | initialDelaySeconds: 5 42 | periodSeconds: 10 43 | # TODO(user): Configure the resources accordingly based on the project requirements. 44 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 45 | resources: 46 | limits: 47 | cpu: 500m 48 | memory: 128Mi 49 | requests: 50 | cpu: 10m 51 | memory: 64Mi 52 | serviceAccountName: controller-manager 53 | terminationGracePeriodSeconds: 10 54 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-containers-nonroot.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | runAsNonRoot: false 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-containers-priv.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | privileged: true 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-containers-readonly-root.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | readOnlyRootFilesystem: false 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-containers-rootuser.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | runAsUser: 0 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-none.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | livenessProbe: 28 | httpGet: 29 | path: /healthz 30 | port: 8081 31 | initialDelaySeconds: 15 32 | periodSeconds: 20 33 | readinessProbe: 34 | httpGet: 35 | path: /readyz 36 | port: 8081 37 | initialDelaySeconds: 5 38 | periodSeconds: 10 39 | # TODO(user): Configure the resources accordingly based on the project requirements. 40 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 41 | resources: 42 | limits: 43 | cpu: 500m 44 | memory: 128Mi 45 | requests: 46 | cpu: 10m 47 | memory: 64Mi 48 | serviceAccountName: controller-manager 49 | terminationGracePeriodSeconds: 10 50 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-spec-all.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | allowPrivilegeEscalation: false 22 | privileged: false 23 | readOnlyRootFilesystem: true 24 | runAsNonRoot: true 25 | runAsGroup: 25000 26 | runAsUser: 20000 27 | capabilities: 28 | drop: 29 | - ALL 30 | containers: 31 | - command: 32 | - /manager 33 | args: 34 | - --leader-elect 35 | image: controller:latest 36 | name: manager 37 | livenessProbe: 38 | httpGet: 39 | path: /healthz 40 | port: 8081 41 | initialDelaySeconds: 15 42 | periodSeconds: 20 43 | readinessProbe: 44 | httpGet: 45 | path: /readyz 46 | port: 8081 47 | initialDelaySeconds: 5 48 | periodSeconds: 10 49 | # TODO(user): Configure the resources accordingly based on the project requirements. 50 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 51 | resources: 52 | limits: 53 | cpu: 500m 54 | memory: 128Mi 55 | requests: 56 | cpu: 10m 57 | memory: 64Mi 58 | serviceAccountName: controller-manager 59 | terminationGracePeriodSeconds: 10 60 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-spec-allow-priv.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | allowPrivilegeEscalation: true 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-spec-cap-sysadmin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | capabilities: 22 | add: 23 | - SYS_ADMIN 24 | containers: 25 | - command: 26 | - /manager 27 | args: 28 | - --leader-elect 29 | image: controller:latest 30 | name: manager 31 | livenessProbe: 32 | httpGet: 33 | path: /healthz 34 | port: 8081 35 | initialDelaySeconds: 15 36 | periodSeconds: 20 37 | readinessProbe: 38 | httpGet: 39 | path: /readyz 40 | port: 8081 41 | initialDelaySeconds: 5 42 | periodSeconds: 10 43 | # TODO(user): Configure the resources accordingly based on the project requirements. 44 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 45 | resources: 46 | limits: 47 | cpu: 500m 48 | memory: 128Mi 49 | requests: 50 | cpu: 10m 51 | memory: 64Mi 52 | serviceAccountName: controller-manager 53 | terminationGracePeriodSeconds: 10 54 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-spec-nonroot.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | runAsNonRoot: false 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-spec-priv.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | privileged: true 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-spec-readonly-root.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | readOnlyRootFilesystem: false 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/deploy-sc-spec-rootuser.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | runAsUser: 0 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | # TODO(user): Configure the resources accordingly based on the project requirements. 42 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 10m 49 | memory: 64Mi 50 | serviceAccountName: controller-manager 51 | terminationGracePeriodSeconds: 10 52 | -------------------------------------------------------------------------------- /test/asset/ns-dedicated.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: system 5 | -------------------------------------------------------------------------------- /test/asset/ns-default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: default 5 | -------------------------------------------------------------------------------- /test/asset/ns-kubesystem.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kube-system 5 | -------------------------------------------------------------------------------- /test/asset/reg-empty-file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/controlplaneio/badrobot/21d0ef4861bcb72aca4e51f45b5853dfa4f16205/test/asset/reg-empty-file -------------------------------------------------------------------------------- /test/asset/reg-empty-json-file: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/asset/reg-invalid-kind.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment2 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | securityContext: 28 | allowPrivilegeEscalation: false 29 | privileged: false 30 | readOnlyRootFilesystem: true 31 | runAsNonRoot: true 32 | runAsGroup: 25000 33 | runAsUser: 20000 34 | capabilities: 35 | drop: 36 | - ALL 37 | livenessProbe: 38 | httpGet: 39 | path: /healthz 40 | port: 8081 41 | initialDelaySeconds: 15 42 | periodSeconds: 20 43 | readinessProbe: 44 | httpGet: 45 | path: /readyz 46 | port: 8081 47 | initialDelaySeconds: 5 48 | periodSeconds: 10 49 | # TODO(user): Configure the resources accordingly based on the project requirements. 50 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 51 | resources: 52 | limits: 53 | cpu: 500m 54 | memory: 128Mi 55 | requests: 56 | cpu: 10m 57 | memory: 64Mi 58 | serviceAccountName: controller-manager 59 | terminationGracePeriodSeconds: 10 60 | -------------------------------------------------------------------------------- /test/asset/reg-invalid-schema.yaml: -------------------------------------------------------------------------------- 1 | # older api schema (v1beta1) 2 | apiVersion: apps/v1beta1 3 | kind: Deployment2 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | labels: 8 | control-plane: controller-manager 9 | spec: 10 | # selector defined which is not required for spec 11 | selector: 12 | matchLabels: 13 | control-plane: controller-manager 14 | replicas: 1 15 | template: 16 | metadata: 17 | annotations: 18 | kubectl.kubernetes.io/default-container: manager 19 | labels: 20 | control-plane: controller-manager 21 | spec: 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: controller:latest 28 | name: manager 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | privileged: false 32 | readOnlyRootFilesystem: true 33 | runAsNonRoot: true 34 | runAsGroup: 25000 35 | runAsUser: 20000 36 | capabilities: 37 | drop: 38 | - ALL 39 | livenessProbe: 40 | httpGet: 41 | path: /healthz 42 | port: 8081 43 | initialDelaySeconds: 15 44 | periodSeconds: 20 45 | readinessProbe: 46 | httpGet: 47 | path: /readyz 48 | port: 8081 49 | initialDelaySeconds: 5 50 | periodSeconds: 10 51 | # TODO(user): Configure the resources accordingly based on the project requirements. 52 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 53 | resources: 54 | limits: 55 | cpu: 500m 56 | memory: 128Mi 57 | requests: 58 | cpu: 10m 59 | memory: 64Mi 60 | serviceAccountName: controller-manager 61 | terminationGracePeriodSeconds: 10 62 | --------------------------------------------------------------------------------