Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Factory:Rebuild
kubeaudit
kubeaudit-0.22.2.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File kubeaudit-0.22.2.obscpio of Package kubeaudit
07070100000000000081A400000000000000000000000166C635DC0000002E000000000000000000000000000000000000001F00000000kubeaudit-0.22.2/.dockerignore.* docs/ Makefile LICENSE *.md *.sh Dockerfile07070100000001000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001900000000kubeaudit-0.22.2/.github07070100000002000081A400000000000000000000000166C635DC00000665000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/.github/ISSUE_TEMPLATE.md# 🚨 Deprecation Notice 🚨 Kubeaudit is planned for deprecation by October 2024. We are actively seeking maintainers who are interested in taking over the stewardship of this project. If you are passionate about continuing its development and maintenance, please reach out to us. For users looking for alternatives, we recommend transitioning to Kubebench, which offers similar functionality and is actively maintained. Thank you to the community for your contributions and support. <!-- Please erase any parts of this template not applicable to your issue. --> ##### ISSUE TYPE - [ ] Bug Report - [ ] Feature Idea # BUG REPORT ##### SUMMARY <!-- Briefly describe the problem/enhancement. --> ##### ENVIRONMENT * Kubeaudit version: X.Y.Z * Kubeaudit install method: DIY-BUILD/Binary/Kubectl-Plugin ##### STEPS TO REPRODUCE <!-- Please describe exactly how to reproduce the problem. --> ##### EXPECTED RESULTS <!-- What did you expect to happen when running the steps above? --> ##### ACTUAL RESULTS <!-- What actually happened? --> ##### ADDITIONAL INFORMATION <!-- Include any screenshots or other information. --> # FEATURE IDEA - [ ] If the maintainers agree with the feature as described here, I intend to submit a Pull Request myself.<sup>1</sup> **Proposal:** <!-- provide details on the behaviour you'd like to see and why it would be useful --> <sup><small>1</small></sup> <sub>This is the quickest way to get a new feature! We reserve the right to close feature requests, even ones we like, if the proposer does not intend to contribute to the feature and it doesn't fit in our current roadmap.</sub> 07070100000003000081A400000000000000000000000166C635DC000007A0000000000000000000000000000000000000003200000000kubeaudit-0.22.2/.github/PULL_REQUEST_TEMPLATE.md<!-- Please erase any parts of this template not applicable to your Pull Request. --> <!-- All code PR must be labeled with :bug: (patch fixes), :sparkles: (backwards-compatible features), or :warning: (breaking changes) --> # 🚨 Deprecation Notice 🚨 Kubeaudit is planned for deprecation by October 2024. We are actively seeking maintainers who are interested in taking over the stewardship of this project. If you are passionate about continuing its development and maintenance, please reach out to us. For users looking for alternatives, we recommend transitioning to Kubebench, which offers similar functionality and is actively maintained. Thank you to the community for your contributions and support. ##### Description <!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes # (issue) ##### Type of change <!-- Please delete options that are not relevant. ---> - [ ] Bug fix :bug: - [ ] New feature :sparkles: - [ ] This change requires a documentation update :book: - [ ] Breaking changes :warning: ##### How Has This Been Tested? <!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration --> - [ ] Test A - [ ] Test B ##### Checklist: - [ ] I have :tophat: my changes (A 🎩 specifically includes pulling down changes, setting them up, and manually testing the changed features and potential side effects to make sure nothing is broken) - [ ] I have performed a self-review of my own code - [ ] I have made corresponding changes to the documentation - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] The test coverage did not decrease - [ ] I have signed the appropriate [Contributor License Agreement](https://cla.shopify.com/) 07070100000004000081A400000000000000000000000166C635DC00000080000000000000000000000000000000000000002800000000kubeaudit-0.22.2/.github/dependabot.ymlversion: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily open-pull-requests-limit: 100 07070100000005000081A400000000000000000000000166C635DC000000BD000000000000000000000000000000000000002500000000kubeaudit-0.22.2/.github/labeler.ymlcore: - '/cmd' legal: - any: ['LICENSE*', 'CODE_OF_CONDUCT*'] config: - any: ['.github','build','Makefile','/config'] go-modules: - 'go.*' readme: - 'README*' datafiles: - '/fixtures' 07070100000006000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002300000000kubeaudit-0.22.2/.github/workflows07070100000007000081A400000000000000000000000166C635DC00000259000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/.github/workflows/ci.ymlname: CI on: push: branches: - main pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v2 with: go-version-file: "go.mod" - name: Clone repo uses: actions/checkout@v2 - name: Install kubectl run: sudo snap install kubectl --classic - name: Install kind run: go get sigs.k8s.io/kind - name: Go mod download and go tidy run: make setup - name: Run tests env: USE_KIND: "true" run: make test 07070100000008000081A400000000000000000000000166C635DC0000025B000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/.github/workflows/cla.ymlname: Contributor License Agreement (CLA) on: pull_request_target: types: [opened, synchronize, reopened] issue_comment: types: [created] jobs: cla: runs-on: ubuntu-latest if: | (github.event.issue.pull_request && !github.event.issue.pull_request.merged_at && contains(github.event.comment.body, 'signed') ) || (github.event.pull_request && !github.event.pull_request.merged) steps: - uses: Shopify/shopify-cla-action@v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} cla-token: ${{ secrets.CLA_TOKEN }} 07070100000009000081A400000000000000000000000166C635DC00000240000000000000000000000000000000000000003900000000kubeaudit-0.22.2/.github/workflows/first-interaction.ymlname: Notify new contributors on: pull_request: types: - opened issues: types: - opened jobs: notify: runs-on: ubuntu-latest steps: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: 'Thanks for opening your first issue here! Be sure to follow the issue template!' pr-message: 'Thanks for opening this pull request! Please check out our [contributing guidelines](https://github.com/Shopify/kubeaudit#Contributing) and [sign the CLA](https://cla.shopify.com/).' 0707010000000A000081A400000000000000000000000166C635DC000000F7000000000000000000000000000000000000003200000000kubeaudit-0.22.2/.github/workflows/pr-labeler.ymlname: "Pull Request Labeler" on: - pull_request_target jobs: triage: permissions: pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/labeler@v4 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" 0707010000000B000081A400000000000000000000000166C635DC00000428000000000000000000000000000000000000002F00000000kubeaudit-0.22.2/.github/workflows/release.ymlname: release on: push: tags: [ v*.*.* ] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: release: runs-on: ubuntu-latest permissions: contents: write packages: write steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - name: Log into registry ${{ env.REGISTRY }} uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.22.1 check-latest: true cache: true - name: Release uses: goreleaser/goreleaser-action@b508e2e3ef3b19d4e4146d4f8fb3ba9db644a757 with: distribution: goreleaser version: v1.10.3 args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 0707010000000C000081A400000000000000000000000166C635DC0000007A000000000000000000000000000000000000001C00000000kubeaudit-0.22.2/.gitignore/kubeaudit /kubeaudit_unix /tmp /.glide /.dev .DS_Store coverage.txt /dist *.swp /vendor /.vscode .go-version profile.out 0707010000000D000081A400000000000000000000000166C635DC00000476000000000000000000000000000000000000002100000000kubeaudit-0.22.2/.goreleaser.ymlproject_name: kubeaudit release: github: owner: Shopify name: kubeaudit draft: true name_template: "{{.ProjectName}}-v{{.Version}}" dockers: - dockerfile: goreleaser.Dockerfile goos: linux goarch: amd64 goarm: '' image_templates: - "ghcr.io/shopify/kubeaudit:latest" - "ghcr.io/shopify/kubeaudit:{{ .Tag }}" - "ghcr.io/shopify/kubeaudit:v{{ .Major }}.{{ .Minor }}" builds: - goos: - linux - darwin - windows goarm: - 6 - 7 main: ./cmd/main.go binary: kubeaudit ldflags: - -s -w -X github.com/Shopify/kubeaudit/cmd.Version={{.Version}} -X github.com/Shopify/kubeaudit/cmd.Commit={{.Commit}} -X github.com/Shopify/kubeaudit/cmd.BuildDate={{.Date}} changelog: sort: asc filters: exclude: - "^docs:" - "^test:" - ^Merge archives: - format: tar.gz name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{.Arm }}{{ end }}' files: - licence* - LICENCE* - readme* - README* - changelog* - CHANGELOG* snapshot: name_template: SNAPSHOT-{{ .Commit }} checksum: name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt' 0707010000000E000081A400000000000000000000000166C635DC00000C78000000000000000000000000000000000000002400000000kubeaudit-0.22.2/CODE_OF_CONDUCT.md# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource@shopify.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/ [homepage]: https://www.contributor-covenant.org 0707010000000F000081A400000000000000000000000166C635DC0000052B000000000000000000000000000000000000001C00000000kubeaudit-0.22.2/DockerfileFROM golang:1.22.1 AS builder # no need to include cgo bindings ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 # add ca certificates and timezone data files # hadolint ignore=DL3008 RUN apt-get install --yes --no-install-recommends ca-certificates tzdata # add unprivileged user RUN adduser --shell /bin/true --uid 1000 --disabled-login --no-create-home --gecos '' app \ && sed -i -r "/^(app|root)/!d" /etc/group /etc/passwd \ && sed -i -r 's#^(.*):[^:]*$#\1:/sbin/nologin#' /etc/passwd # this is where we build our app WORKDIR /go/src/app/ # download and cache our dependencies VOLUME /go/pkg/mod COPY go.mod go.sum ./ RUN go mod download # compile kubeaudit COPY . ./ RUN go build -a -ldflags '-w -s -extldflags "-static"' -o /go/bin/kubeaudit ./cmd/ \ && chmod +x /go/bin/kubeaudit # # --- # # start with empty image FROM scratch # add-in our timezone data file COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo # add-in our unprivileged user COPY --from=builder /etc/passwd /etc/group /etc/shadow /etc/ # add-in our ca certificates COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # add-in our application COPY --from=builder --chown=app /go/bin/kubeaudit /kubeaudit # from now on, run as the unprivileged user USER 1000 # entrypoint ENTRYPOINT ["/kubeaudit"] CMD ["all"] 07070100000010000081A400000000000000000000000166C635DC00000433000000000000000000000000000000000000001900000000kubeaudit-0.22.2/LICENSEThe MIT License (MIT) Copyright 2017 Shopify Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 07070100000011000081A400000000000000000000000166C635DC00000659000000000000000000000000000000000000001A00000000kubeaudit-0.22.2/Makefile# Go parameters GOCMD=go GOBUILD=$(GOCMD) build GOCLEAN=$(GOCMD) clean GOTEST=$(GOCMD) test GOMOD=$(GOCMD) mod BINARY_NAME=kubeaudit BINARY_UNIX=$(BINARY_NAME)_unix LDFLAGS=$(shell build/ldflags.sh) # kubernetes client won't build with go<1.10 GOVERSION:=$(shell go version | awk '{print $$3}') GOVERSION_MIN:=go1.22.1 GOVERSION_CHECK=$(shell printf "%s\n%s\n" "$(GOVERSION)" "$(GOVERSION_MIN)" | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | head -n 1) # Test parameters TEST_CLUSTER_NAME="kubeaudit-test" export GO111MODULE=on ifneq ($(GOVERSION_MIN), $(GOVERSION_CHECK)) $(error Detected Go version $(GOVERSION) < required version $(GOVERSION_MIN)) endif all: test build build: $(GOBUILD) -o $(BINARY_NAME) -v -ldflags=all="$(LDFLAGS)" cmd/main.go install: cp $(BINARY_NAME) $(GOPATH)/bin/kubeaudit plugin: cp $(BINARY_NAME) $(GOPATH)/bin/kubectl-audit test: ./test.sh test-setup: kind create cluster --name ${TEST_CLUSTER_NAME} --image kindest/node:v1.20.15@sha256:6f2d011dffe182bad80b85f6c00e8ca9d86b5b8922cdf433d53575c4c5212248 test-teardown: kind delete cluster --name ${TEST_CLUSTER_NAME} show-coverage: test go tool cover -html=coverage.txt setup: $(GOMOD) download $(GOMOD) tidy clean: $(GOCLEAN) rm -f $(BINARY_NAME) rm -f $(BINARY_UNIX) # Cross Compilation build-linux: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v docker-build: docker run --rm -it -v "$(GOPATH)":/go -w /go/src/github.com/Shopify/kubeaudit golang:1.12 go build -o "$(BINARY_UNIX)" -v .PHONY: all build install plugin test test-setup test-teardown show-coverage clean build-linux docker-build 07070100000012000081A400000000000000000000000166C635DC0000484F000000000000000000000000000000000000001B00000000kubeaudit-0.22.2/README.md[![Build Status](https://github.com/Shopify/kubeaudit/actions/workflows/ci.yml/badge.svg)](https://github.com/Shopify/kubeaudit/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/Shopify/kubeaudit)](https://goreportcard.com/report/github.com/Shopify/kubeaudit) [![GoDoc](https://godoc.org/github.com/Shopify/kubeaudit?status.png)](https://godoc.org/github.com/Shopify/kubeaudit) > It is now a requirement for clusters to run Kubernetes >=1.19. > override labels with unregistered `kubernetes.io` annotations will be deprecated. It'll soon be a requirement to use `kubeaudit.io` instead. Refer to this [discussion](https://github.com/Shopify/kubeaudit/issues/457) for additional context. # 🚨 Deprecation Notice 🚨 Kubeaudit is planned for deprecation by October 2024. We are actively seeking maintainers who are interested in taking over the stewardship of this project. If you are passionate about continuing its development and maintenance, please reach out to us. For users looking for alternatives, we recommend transitioning to Kubebench, which offers similar functionality and is actively maintained. Thank you to the community for your contributions and support. # kubeaudit :cloud: :lock: :muscle: `kubeaudit` is a command line tool and a Go package to audit Kubernetes clusters for various different security concerns, such as: * run as non-root * use a read-only root filesystem * drop scary capabilities, don't add new ones * don't run privileged * and more! **tldr. `kubeaudit` makes sure you deploy secure containers!** ## Package To use kubeaudit as a Go package, see the [package docs](https://pkg.go.dev/github.com/Shopify/kubeaudit). The rest of this README will focus on how to use kubeaudit as a command line tool. ## Command Line Interface (CLI) * [Installation](#installation) * [Quick Start](#quick-start) * [Audit Results](#audit-results) * [Commands](#commands) * [Configuration File](#configuration-file) * [Override Errors](#override-errors) * [Contributing](#contributing) ## Installation ### Brew ``` brew install kubeaudit ``` ### Download a binary Kubeaudit has official releases that are blessed and stable: [Official releases](https://github.com/Shopify/kubeaudit/releases) ### DIY build Main may have newer features than the stable releases. If you need a newer feature not yet included in a release, make sure you're using the latest Go and run the following: ```sh go get -v github.com/Shopify/kubeaudit ``` Start using `kubeaudit` with the [Quick Start](#quick-start) or view all the [supported commands](#commands). ### Kubectl Plugin Prerequisite: kubectl v1.12.0 or later With kubectl v1.12.0 introducing [easy pluggability](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/) of external functions, kubeaudit can be invoked as `kubectl audit` by - running `make plugin` and having `$GOPATH/bin` available in your path. or - renaming the binary to `kubectl-audit` and having it available in your path. ### Docker We no longer release images to Docker Hub (since Docker Hub sunset Free Team organizations). For the time being, [old images](https://hub.docker.com/r/shopify/kubeaudit) are still available but may stop being available at any time. We will start publishing images to the Github Container registry soon. To run kubeaudit as a job in your cluster see [Running kubeaudit in a cluster](docs/cluster.md). ## Quick Start kubeaudit has three modes: 1. Manifest mode 1. Local mode 1. Cluster mode ### Manifest Mode If a Kubernetes manifest file is provided using the `-f/--manifest` flag, kubeaudit will audit the manifest file. Example command: ``` kubeaudit all -f "/path/to/manifest.yml" ``` Example output: ``` $ kubeaudit all -f "internal/test/fixtures/all_resources/deployment-apps-v1.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: deployment-apps-v1 -------------------------------------------- -- [error] AppArmorAnnotationMissing Message: AppArmor annotation missing. The annotation 'container.apparmor.security.beta.kubernetes.io/container' should be added. Metadata: Container: container MissingAnnotation: container.apparmor.security.beta.kubernetes.io/container -- [error] AutomountServiceAccountTokenTrueAndDefaultSA Message: Default service account with token mounted. automountServiceAccountToken should be set to 'false' or a non-default service account should be used. -- [error] CapabilityShouldDropAll Message: Capability not set to ALL. Ideally, you should drop ALL capabilities and add the specific ones you need to the add list. Metadata: Container: container Capability: AUDIT_WRITE ... ``` If no errors with a given minimum severity are found, the following is returned: ```shell All checks completed. 0 high-risk vulnerabilities found ``` #### Autofix Manifest mode also supports autofixing all security issues using the `autofix` command: ``` kubeaudit autofix -f "/path/to/manifest.yml" ``` To write the fixed manifest to a new file instead of modifying the source file, use the `-o/--output` flag. ``` kubeaudit autofix -f "/path/to/manifest.yml" -o "/path/to/fixed" ``` To fix a manifest based on custom rules specified on a kubeaudit config file, use the `-k/--kconfig` flag. ``` kubeaudit autofix -k "/path/to/kubeaudit-config.yml" -f "/path/to/manifest.yml" -o "/path/to/fixed" ``` ### Cluster Mode Kubeaudit can detect if it is running within a container in a cluster. If so, it will try to audit all Kubernetes resources in that cluster: ``` kubeaudit all ``` ### Local Mode Kubeaudit will try to connect to a cluster using the local kubeconfig file (`$HOME/.kube/config`). A different kubeconfig location can be specified using the `--kubeconfig` flag. To specify a context of the kubeconfig, use the `-c/--context` flag. ``` kubeaudit all --kubeconfig "/path/to/config" --context my_cluster ``` For more information on kubernetes config files, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/ ## Audit Results Kubeaudit produces results with three levels of severity: - `Error`: A security issue or invalid kubernetes configuration - `Warning`: A best practice recommendation - `Info`: Informational, no action required. This includes results that are [overridden](#override-errors) The minimum severity level can be set using the `--minSeverity/-m` flag. By default kubeaudit will output results in a human-readable way. If the output is intended to be further processed, it can be set to output JSON using the `--format json` flag. To output results as logs (the previous default) use `--format logrus`. Some output formats include colors to make results easier to read in a terminal. To disable colors (for example, if you are sending output to a text file), you can use the `--no-color` flag. You can generate a kubeaudit report in [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html) using the `--format sarif` flag. To write the SARIF results to a file, you can redirect the output with `>`. For example: ``` kubeaudit all -f path-to-my-file.yaml --format="sarif" > example.sarif ``` If there are results of severity level `error`, kubeaudit will exit with exit code 2. This can be changed using the `--exitcode/-e` flag. For all the ways kubeaudit can be customized, see [Global Flags](#global-flags). ## Commands | Command | Description | Documentation | | :-------- | :------------------------------------------------------------------------ | :---------------------- | | `all` | Runs all available auditors, or those specified using a kubeaudit config. | [docs](docs/all.md) | | `autofix` | Automatically fixes security issues. | [docs](docs/autofix.md) | | `version` | Prints the current kubeaudit version. | | ### Auditors Auditors can also be run individually. | Command | Description | Documentation | | :--------------- | :------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | | `apparmor` | Finds containers running without AppArmor. | [docs](docs/auditors/apparmor.md) | | `asat` | Finds pods using an automatically mounted default service account | [docs](docs/auditors/asat.md) | | `capabilities` | Finds containers that do not drop the recommended capabilities or add new ones. | [docs](docs/auditors/capabilities.md) | | `deprecatedapis` | Finds any resource defined with a deprecated API version. | [docs](docs/auditors/deprecatedapis.md) | | `hostns` | Finds containers that have HostPID, HostIPC or HostNetwork enabled. | [docs](docs/auditors/hostns.md) | | `image` | Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag. | [docs](docs/auditors/image.md) | | `limits` | Finds containers which exceed the specified CPU and memory limits or do not specify any. | [docs](docs/auditors/limits.md) | | `mounts` | Finds containers that have sensitive host paths mounted. | [docs](docs/auditors/mounts.md) | | `netpols` | Finds namespaces that do not have a default-deny network policy. | [docs](docs/auditors/netpols.md) | | `nonroot` | Finds containers running as root. | [docs](docs/auditors/nonroot.md) | | `privesc` | Finds containers that allow privilege escalation. | [docs](docs/auditors/privesc.md) | | `privileged` | Finds containers running as privileged. | [docs](docs/auditors/privileged.md) | | `rootfs` | Finds containers which do not have a read-only filesystem. | [docs](docs/auditors/rootfs.md) | | `seccomp` | Finds containers running without Seccomp. | [docs](docs/auditors/seccomp.md) | ### Global Flags | Short | Long | Description | | :---- | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | | | --format | The output format to use (one of "sarif", "pretty", "logrus", "json") (default is "pretty") | | | --kubeconfig | Path to local Kubernetes config file. Only used in local mode (default is `$HOME/.kube/config`) | | -c | --context | The name of the kubeconfig context to use | | -f | --manifest | Path to the yaml configuration to audit. Only used in manifest mode. You may use `-` to read from stdin. | | -n | --namespace | Only audit resources in the specified namespace. Not currently supported in manifest mode. | | -g | --includegenerated | Include generated resources in scan (such as Pods generated by deployments). If you would like kubeaudit to produce results for generated resources (for example if you have custom resources or want to catch orphaned resources where the owner resource no longer exists) you can use this flag. | | -m | --minseverity | Set the lowest severity level to report (one of "error", "warning", "info") (default is "info") | | -e | --exitcode | Exit code to use if there are results with severity of "error". Conventionally, 0 is used for success and all non-zero codes for an error. (default is 2) | | | --no-color | Don't use colors in the output (default is false) | ## Configuration File The kubeaudit config can be used for two things: 1. Enabling only some auditors 1. Specifying configuration for auditors Any configuration that can be specified using flags for the individual auditors can be represented using the config. The config has the following format: ```yaml enabledAuditors: # Auditors are enabled by default if they are not explicitly set to "false" apparmor: false asat: false capabilities: true deprecatedapis: true hostns: true image: true limits: true mounts: true netpols: true nonroot: true privesc: true privileged: true rootfs: true seccomp: true auditors: capabilities: # add capabilities needed to the add list, so kubeaudit won't report errors allowAddList: ['AUDIT_WRITE', 'CHOWN'] deprecatedapis: # If no versions are specified and the'deprecatedapis' auditor is enabled, WARN # results will be genereted for the resources defined with a deprecated API. currentVersion: '1.22' targetedVersion: '1.25' image: # If no image is specified and the 'image' auditor is enabled, WARN results # will be generated for containers which use an image without a tag image: 'myimage:mytag' limits: # If no limits are specified and the 'limits' auditor is enabled, WARN results # will be generated for containers which have no cpu or memory limits specified cpu: '750m' memory: '500m' ``` For more details about each auditor, including a description of the auditor-specific configuration in the config, see the [Auditor Docs](#auditors). **Note**: The kubeaudit config is not the same as the kubeconfig file specified with the `--kubeconfig` flag, which refers to the Kubernetes config file (see [Local Mode](/README.md#local-mode)). Also note that only the `all` and `autofix` commands support using a kubeaudit config. It will not work with other commands. **Note**: If flags are used in combination with the config file, flags will take precedence. ## Override Errors Security issues can be ignored for specific containers or pods by adding override labels. This means the auditor will produce `info` results instead of `error` results and the audit result name will have `Allowed` appended to it. The labels are documented in each auditor's documentation, but the general format for auditors that support overrides is as follows: An override label consists of a `key` and a `value`. The `key` is a combination of the override type (container or pod) and an `override identifier` which is unique to each auditor (see the [docs](#auditors) for the specific auditor). The `key` can take one of two forms depending on the override type: 1. **Container overrides**, which override the auditor for that specific container, are formatted as follows: ```yaml container.kubeaudit.io/[container name].[override identifier] ``` 2. **Pod overrides**, which override the auditor for all containers within the pod, are formatted as follows: ```yaml kubeaudit.io/[override identifier] ``` If the `value` is set to a non-empty string, it will be displayed in the `info` result as the `OverrideReason`: ``` $ kubeaudit asat -f "auditors/asat/fixtures/service-account-token-true-allowed.yml" ---------------- Results for --------------- apiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-true-allowed -------------------------------------------- -- [info] AutomountServiceAccountTokenTrueAndDefaultSAAllowed Message: Audit result overridden: Default service account with token mounted. automountServiceAccountToken should be set to 'false' or a non-default service account should be used. Metadata: OverrideReason: SomeReason ``` As per Kubernetes spec, `value` must be 63 characters or less and must be empty or begin and end with an alphanumeric character (`[a-z0-9A-Z]`) with dashes (`-`), underscores (`_`), dots (`.`), and alphanumerics between. Multiple override labels (for multiple auditors) can be added to the same resource. See the specific [auditor docs](#auditors) for the auditor you wish to override for examples. To learn more about labels, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ ## Contributing If you'd like to fix a bug, contribute a feature or just correct a typo, please feel free to do so as long as you follow our [Code of Conduct](./CODE_OF_CONDUCT.md). 1. Create your own fork! 1. Get the source: `go get github.com/Shopify/kubeaudit` 1. Go to the source: `cd $GOPATH/src/github.com/Shopify/kubeaudit` 1. Add your forked repo as a fork: `git remote add fork https://github.com/you-are-awesome/kubeaudit` 1. Create your feature branch: `git checkout -b awesome-new-feature` 1. Install [Kind](https://kind.sigs.k8s.io/#installation-and-usage) 1. Run the tests to see everything is working as expected: `USE_KIND=true make test` (to run tests without Kind: `make test`) 1. Commit your changes: `git commit -am 'Adds awesome feature'` 1. Push to the branch: `git push fork` 1. Sign the [Contributor License Agreement](https://cla.shopify.com/) 1. Submit a PR (All PR must be labeled with :bug: (Bug fix), :sparkles: (New feature), :book: (Documentation update), or :warning: (Breaking changes) ) 1. ??? 1. Profit Note that if you didn't sign the CLA before opening your PR, you can re-run the check by adding a comment to the PR that says "I've signed the CLA!"! 070701000000130000A1FF00000000000000000000000166C635DC00000014000000000000000000000000000000000000001900000000kubeaudit-0.22.2/VERSIONcmd/commands/VERSION07070100000014000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001A00000000kubeaudit-0.22.2/auditors07070100000015000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001E00000000kubeaudit-0.22.2/auditors/all07070100000016000081A400000000000000000000000166C635DC00000BBB000000000000000000000000000000000000002500000000kubeaudit-0.22.2/auditors/all/all.gopackage all import ( "errors" "fmt" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/asat" "github.com/Shopify/kubeaudit/auditors/capabilities" "github.com/Shopify/kubeaudit/auditors/deprecatedapis" "github.com/Shopify/kubeaudit/auditors/hostns" "github.com/Shopify/kubeaudit/auditors/image" "github.com/Shopify/kubeaudit/auditors/limits" "github.com/Shopify/kubeaudit/auditors/mounts" "github.com/Shopify/kubeaudit/auditors/netpols" "github.com/Shopify/kubeaudit/auditors/nonroot" "github.com/Shopify/kubeaudit/auditors/privesc" "github.com/Shopify/kubeaudit/auditors/privileged" "github.com/Shopify/kubeaudit/auditors/rootfs" "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/Shopify/kubeaudit/config" ) var ErrUnknownAuditor = errors.New("Unknown auditor") var AuditorNames = []string{ apparmor.Name, asat.Name, capabilities.Name, deprecatedapis.Name, hostns.Name, image.Name, limits.Name, mounts.Name, netpols.Name, nonroot.Name, privesc.Name, privileged.Name, rootfs.Name, seccomp.Name, } func Auditors(conf config.KubeauditConfig) ([]kubeaudit.Auditable, error) { auditors := []kubeaudit.Auditable{} for _, auditorName := range getEnabledAuditors(conf) { auditor, err := initAuditor(auditorName, conf) if err != nil { return nil, err } auditors = append(auditors, auditor) } return auditors, nil } // getEnabledAuditors returns a list of all auditors excluding any explicitly disabled in the config func getEnabledAuditors(conf config.KubeauditConfig) []string { auditors := []string{} for _, auditorName := range AuditorNames { // if value is not found in the `conf.GetEnabledAuditors()` map, this means // it wasn't added to the config file, so it should be enabled by default if enabled, ok := conf.GetEnabledAuditors()[auditorName]; !ok || enabled { auditors = append(auditors, auditorName) } } return auditors } func initAuditor(name string, conf config.KubeauditConfig) (kubeaudit.Auditable, error) { switch name { case apparmor.Name: return apparmor.New(), nil case asat.Name: return asat.New(), nil case capabilities.Name: return capabilities.New(conf.GetAuditorConfigs().Capabilities), nil case deprecatedapis.Name: return deprecatedapis.New(conf.GetAuditorConfigs().DeprecatedAPIs) case hostns.Name: return hostns.New(), nil case image.Name: return image.New(conf.GetAuditorConfigs().Image), nil case limits.Name: return limits.New(conf.GetAuditorConfigs().Limits) case mounts.Name: return mounts.New(conf.GetAuditorConfigs().Mounts), nil case netpols.Name: return netpols.New(), nil case nonroot.Name: return nonroot.New(), nil case privesc.Name: return privesc.New(), nil case privileged.Name: return privileged.New(), nil case rootfs.Name: return rootfs.New(), nil case seccomp.Name: return seccomp.New(), nil } return nil, fmt.Errorf("unknown auditor %s: %w", name, ErrUnknownAuditor) } 07070100000017000081A400000000000000000000000166C635DC0000154A000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/auditors/all/all_test.gopackage all import ( "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/asat" "github.com/Shopify/kubeaudit/auditors/capabilities" "github.com/Shopify/kubeaudit/auditors/deprecatedapis" "github.com/Shopify/kubeaudit/auditors/mounts" "github.com/Shopify/kubeaudit/auditors/hostns" "github.com/Shopify/kubeaudit/auditors/image" "github.com/Shopify/kubeaudit/auditors/limits" "github.com/Shopify/kubeaudit/auditors/netpols" "github.com/Shopify/kubeaudit/auditors/nonroot" "github.com/Shopify/kubeaudit/auditors/privesc" "github.com/Shopify/kubeaudit/auditors/privileged" "github.com/Shopify/kubeaudit/auditors/rootfs" "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/Shopify/kubeaudit/config" "github.com/Shopify/kubeaudit/internal/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const fixtureDir = "../../internal/test/fixtures/all_resources" func TestAuditAll(t *testing.T) { allErrors := []string{ apparmor.AppArmorAnnotationMissing, asat.AutomountServiceAccountTokenTrueAndDefaultSA, capabilities.CapabilityOrSecurityContextMissing, hostns.NamespaceHostNetworkTrue, hostns.NamespaceHostIPCTrue, hostns.NamespaceHostPIDTrue, image.ImageTagMissing, limits.LimitsNotSet, netpols.MissingDefaultDenyIngressAndEgressNetworkPolicy, nonroot.RunAsNonRootPSCNilCSCNil, privesc.AllowPrivilegeEscalationNil, privileged.PrivilegedNil, rootfs.ReadOnlyRootFilesystemNil, seccomp.SeccompProfileMissing, } allAuditors, err := Auditors( // Not all the tested resources raise an deprecated API error config.KubeauditConfig{EnabledAuditors: map[string]bool{deprecatedapis.Name: false}}) require.NoError(t, err) for _, file := range test.GetAllFileNames(t, fixtureDir) { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) file := file t.Run(file, func(t *testing.T) { t.Parallel() test.AuditMultiple(t, fixtureDir, file, allAuditors, allErrors, "", test.MANIFEST_MODE) test.AuditMultiple(t, fixtureDir, file, allAuditors, allErrors, strings.Split(file, ".")[0], test.LOCAL_MODE) }) } } func TestFixAll(t *testing.T) { allAuditors, err := Auditors(config.KubeauditConfig{}) require.NoError(t, err) files := test.GetAllFileNames(t, fixtureDir) for _, file := range files { t.Run(file, func(t *testing.T) { _, report := test.FixSetupMultiple(t, fixtureDir, file, allAuditors) for _, result := range report.Results() { for _, auditResult := range result.GetAuditResults() { require.NotEqual(t, kubeaudit.Error, auditResult.Severity) } } }) } } // Test all auditors with config func TestAllWithConfig(t *testing.T) { enabledAuditors := []string{ apparmor.Name, seccomp.Name, } expectedErrors := []string{ apparmor.AppArmorAnnotationMissing, seccomp.SeccompProfileMissing, } conf := config.KubeauditConfig{ EnabledAuditors: enabledAuditorsToMap(enabledAuditors), } auditors, err := Auditors(conf) require.NoError(t, err) for _, file := range test.GetAllFileNames(t, fixtureDir) { t.Run(file, func(t *testing.T) { test.AuditMultiple(t, fixtureDir, file, auditors, expectedErrors, "", test.MANIFEST_MODE) }) } } func TestGetEnabledAuditors(t *testing.T) { cases := []struct { testName string enabledAuditors map[string]bool expectedAuditors []string }{ { // If no config is provided, all auditors should be enabled testName: "No config", enabledAuditors: map[string]bool{}, expectedAuditors: AuditorNames, }, { // If some auditors are explicitly disabled, the rest should default to being enabled testName: "Some disabled", enabledAuditors: map[string]bool{ "apparmor": false, "rootfs": false, }, expectedAuditors: []string{ asat.Name, capabilities.Name, deprecatedapis.Name, hostns.Name, image.Name, limits.Name, mounts.Name, netpols.Name, nonroot.Name, privesc.Name, privileged.Name, seccomp.Name, }, }, { testName: "Some enabled", enabledAuditors: map[string]bool{ "apparmor": true, "rootfs": true, }, expectedAuditors: AuditorNames, }, { // If some auditors are explicitly disabled, the rest should default to being enabled testName: "Some enabled, some disabled", enabledAuditors: map[string]bool{ "asat": true, "apparmor": false, "capabilities": true, "rootfs": false, }, expectedAuditors: []string{ asat.Name, capabilities.Name, deprecatedapis.Name, hostns.Name, image.Name, limits.Name, mounts.Name, netpols.Name, nonroot.Name, privesc.Name, privileged.Name, seccomp.Name, }, }, } for _, tc := range cases { t.Run(tc.testName, func(t *testing.T) { conf := config.KubeauditConfig{ EnabledAuditors: tc.enabledAuditors, } got := getEnabledAuditors(conf) assert.ElementsMatch(t, got, tc.expectedAuditors) }) } } func enabledAuditorsToMap(enabledAuditors []string) map[string]bool { enabledAuditorMap := map[string]bool{} for _, auditorName := range AuditorNames { enabledAuditorMap[auditorName] = false } for _, auditorName := range enabledAuditors { enabledAuditorMap[auditorName] = true } return enabledAuditorMap } 07070100000018000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002300000000kubeaudit-0.22.2/auditors/apparmor07070100000019000081A400000000000000000000000166C635DC00001882000000000000000000000000000000000000002F00000000kubeaudit-0.22.2/auditors/apparmor/apparmor.gopackage apparmor import ( "fmt" "strings" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/fix" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "apparmor" const ( // AppArmorAnnotationMissing occurs when the apparmor annotation is missing AppArmorAnnotationMissing = "AppArmorAnnotationMissing" // AppArmorDisabled occurs when the apparmor annotation is set to the unconfined value AppArmorDisabled = "AppArmorDisabled" // AppArmorDisabled occurs when the apparmor annotation is set to a bad value AppArmorBadValue = "AppArmorBadValue" // AppArmorInvalidAnnotation occurs when the apparmor annotation key refers to a container which doesn't exist. This will // prevent the manifest from being applied to a cluster with AppArmor enabled. AppArmorInvalidAnnotation = "AppArmorInvalidAnnotation" ) // As of Jan 14, 2020 these constants are not in the K8s API package, but once they are they should be replaced // https://github.com/kubernetes/kubernetes/blob/master/pkg/security/apparmor/helpers.go#L25 const ( // The prefix to an annotation key specifying a container profile. ContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/" // The profile specifying the runtime default. ProfileRuntimeDefault = "runtime/default" // The profile specifying the unconfined profile. ProfileUnconfined = "unconfined" // The prefix for specifying profiles loaded on the node. ProfileNamePrefix = "localhost/" ) const OverrideLabel = "allow-disabled-apparmor" // AppArmor implements Auditable type AppArmor struct{} func New() *AppArmor { return &AppArmor{} } // Audit checks that AppArmor is enabled for all containers func (a *AppArmor) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult var containerNames []string for _, container := range k8s.GetContainers(resource) { containerName := container.Name containerNames = append(containerNames, containerName) auditResult := auditContainer(container, resource) auditResult = applyDisabledOverride(auditResult, containerName, resource) if auditResult != nil { auditResults = append(auditResults, auditResult) } } auditResults = append(auditResults, auditPodAnnotations(resource, containerNames)...) return auditResults, nil } func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudit.AuditResult { annotations := k8s.GetAnnotations(resource) containerAnnotation := getContainerAnnotation(container) if isAppArmorAnnotationMissing(containerAnnotation, annotations) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: AppArmorAnnotationMissing, Severity: kubeaudit.Error, Message: fmt.Sprintf("AppArmor annotation missing. The annotation '%s' should be added.", containerAnnotation), Metadata: kubeaudit.Metadata{ "Container": container.Name, "MissingAnnotation": containerAnnotation, }, PendingFix: &fix.ByAddingPodAnnotation{ Key: containerAnnotation, Value: ProfileRuntimeDefault, }, } } if isAppArmorDisabled(containerAnnotation, annotations) { var rule string if isAppArmorUnconfined(containerAnnotation, annotations) { rule = AppArmorDisabled } else { rule = AppArmorBadValue } return &kubeaudit.AuditResult{ Auditor: Name, Rule: rule, Message: fmt.Sprintf("AppArmor is disabled. The apparmor annotation should be set to '%s' or start with '%s'.", ProfileRuntimeDefault, ProfileNamePrefix), Severity: kubeaudit.Error, Metadata: kubeaudit.Metadata{ "Container": container.Name, "Annotation": containerAnnotation, "AnnotationValue": getProfileName(containerAnnotation, annotations), }, PendingFix: &fix.BySettingPodAnnotation{ Key: containerAnnotation, Value: ProfileRuntimeDefault, }, } } return nil } func applyDisabledOverride(auditResult *kubeaudit.AuditResult, containerName string, resource k8s.Resource) *kubeaudit.AuditResult { if auditResult == nil || auditResult.Rule != AppArmorDisabled { return auditResult } return override.ApplyOverride(auditResult, Name, containerName, resource, OverrideLabel) } func auditPodAnnotations(resource k8s.Resource, containerNames []string) []*kubeaudit.AuditResult { var auditResults []*kubeaudit.AuditResult for annotationKey, annotationValue := range k8s.GetAnnotations(resource) { if !strings.HasPrefix(annotationKey, ContainerAnnotationKeyPrefix) { continue } containerName := strings.Split(annotationKey, "/")[1] if !contains(containerNames, containerName) { auditResults = append(auditResults, &kubeaudit.AuditResult{ Auditor: Name, Rule: AppArmorInvalidAnnotation, Severity: kubeaudit.Error, Message: fmt.Sprintf("AppArmor annotation key refers to a container that doesn't exist. Remove the annotation '%s: %s'.", annotationKey, annotationValue), Metadata: kubeaudit.Metadata{ "Container": containerName, "Annotation": fmt.Sprintf("%s: %s", annotationKey, annotationValue), }, PendingFix: &fix.ByRemovingPodAnnotations{ Keys: []string{annotationKey}, }, }) } } return auditResults } func isAppArmorAnnotationMissing(apparmorAnnotation string, annotations map[string]string) bool { _, ok := annotations[apparmorAnnotation] return !ok } func isAppArmorDisabled(apparmorAnnotation string, annotations map[string]string) bool { profileName, ok := annotations[apparmorAnnotation] return !ok || profileName != ProfileRuntimeDefault && !strings.HasPrefix(profileName, ProfileNamePrefix) } func isAppArmorUnconfined(apparmorAnnotation string, annotations map[string]string) bool { profileName, ok := annotations[apparmorAnnotation] return ok && profileName == ProfileUnconfined } func getContainerAnnotation(container *k8s.ContainerV1) string { return ContainerAnnotationKeyPrefix + container.Name } func getProfileName(apparmorAnnotation string, annotations map[string]string) string { profileName := annotations[apparmorAnnotation] return profileName } func contains(arr []string, val string) bool { for _, arrVal := range arr { if arrVal == val { return true } } return false } 0707010000001A000081A400000000000000000000000166C635DC00000AA4000000000000000000000000000000000000003400000000kubeaudit-0.22.2/auditors/apparmor/apparmor_test.gopackage apparmor import ( "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" "github.com/stretchr/testify/assert" ) const fixtureDir = "fixtures" func TestAuditAppArmor(t *testing.T) { cases := []struct { file string expectedErrors []string testLocalMode bool }{ {"apparmor-enabled.yml", nil, true}, {"apparmor-annotation-missing.yml", []string{AppArmorAnnotationMissing}, true}, {"apparmor-annotation-init-container-enabled.yml", nil, true}, {"apparmor-annotation-init-container-missing.yml", []string{AppArmorAnnotationMissing}, true}, {"apparmor-disabled.yml", []string{AppArmorDisabled}, true}, {"apparmor-disabled-overriden.yml", []string{override.GetOverriddenResultName(AppArmorDisabled)}, true}, {"apparmor-disabled-overriden-old-label.yml", []string{override.GetOverriddenResultName(AppArmorDisabled)}, true}, {"apparmor-disabled-overriden-multiple.yml", []string{AppArmorAnnotationMissing, override.GetOverriddenResultName(AppArmorDisabled)}, true}, // These are invalid manifests so we should only test it in manifest mode as kubernetes will fail to apply it {"apparmor-bad-value.yml", []string{AppArmorBadValue}, false}, {"apparmor-bad-value-override.yml", []string{AppArmorBadValue}, false}, {"apparmor-invalid-annotation.yml", []string{AppArmorInvalidAnnotation}, false}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, fixtureDir, tc.file, New(), tc.expectedErrors) if tc.testLocalMode { test.AuditLocal(t, fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) } }) } } func TestFixAppArmor(t *testing.T) { cases := []struct { file string expectedAnnotationValue string }{ {"apparmor-enabled.yml", "localhost/something"}, {"apparmor-annotation-missing.yml", ProfileRuntimeDefault}, {"apparmor-disabled.yml", ProfileRuntimeDefault}, {"apparmor-invalid-annotation.yml", ProfileRuntimeDefault}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, tc.file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) annotations := k8s.GetAnnotations(resource) for _, container := range containers { containerAnnotation := getContainerAnnotation(container) assert.Equal(t, tc.expectedAnnotationValue, annotations[containerAnnotation]) } } }) } } 0707010000001B000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002C00000000kubeaudit-0.22.2/auditors/apparmor/fixtures0707010000001C000081A400000000000000000000000166C635DC00000198000000000000000000000000000000000000005B00000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-annotation-init-container-enabled.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-annotation-init-container-enabled annotations: container.apparmor.security.beta.kubernetes.io/container: localhost/someval container.apparmor.security.beta.kubernetes.io/init-container: localhost/someval spec: initContainers: - name: init-container image: scratch containers: - name: container image: scratch 0707010000001D000081A400000000000000000000000166C635DC00000143000000000000000000000000000000000000005B00000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-annotation-init-container-missing.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-annotation-init-container-missing annotations: container.apparmor.security.beta.kubernetes.io/container: localhost/someval spec: initContainers: - name: init-container image: scratch containers: - name: container image: scratch 0707010000001E000081A400000000000000000000000166C635DC00000097000000000000000000000000000000000000004C00000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-annotation-missing.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-annotation-missing spec: containers: - name: container image: scratch 0707010000001F000081A400000000000000000000000166C635DC00000140000000000000000000000000000000000000004C00000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-bad-value-override.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-bad-value-override annotations: container.apparmor.security.beta.kubernetes.io/container: badval labels: container.kubeaudit.io/container.allow-disabled-apparmor: "SomeReason" spec: containers: - name: container image: scratch 07070100000020000081A400000000000000000000000166C635DC000000E2000000000000000000000000000000000000004300000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-bad-value.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-bad-value annotations: container.apparmor.security.beta.kubernetes.io/container: badval spec: containers: - name: container image: scratch 07070100000021000081A400000000000000000000000166C635DC0000017B000000000000000000000000000000000000005500000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-disabled-overriden-multiple.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-disabled-overriden-multiple annotations: container.apparmor.security.beta.kubernetes.io/container2: unconfined labels: container.kubeaudit.io/container2.allow-disabled-apparmor: "SomeReason" spec: containers: - name: container image: scratch - name: container2 image: scratch 07070100000022000081A400000000000000000000000166C635DC000001B1000000000000000000000000000000000000005600000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-disabled-overriden-old-label.yml# this is to test backwards compatibility with old unregistered annotations (kubernetes.io) apiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-disabled-overriden-old-label annotations: container.apparmor.security.beta.kubernetes.io/container: unconfined labels: container.audit.kubernetes.io/container.allow-disabled-apparmor: "SomeReason" spec: containers: - name: container image: scratch 07070100000023000081A400000000000000000000000166C635DC00000191000000000000000000000000000000000000004C00000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-disabled-overriden.yml# this tests then new kubeaudit labels for overriding errors (kubeaudit.io) apiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-disabled-overriden annotations: container.apparmor.security.beta.kubernetes.io/container: unconfined labels: container.kubeaudit.io/container.allow-disabled-apparmor: "SomeReason" spec: containers: - name: container image: scratch 07070100000024000081A400000000000000000000000166C635DC000000E5000000000000000000000000000000000000004200000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-disabled.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-disabled annotations: container.apparmor.security.beta.kubernetes.io/container: unconfined spec: containers: - name: container image: scratch 07070100000025000081A400000000000000000000000166C635DC000000ED000000000000000000000000000000000000004100000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-enabled.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-enabled annotations: container.apparmor.security.beta.kubernetes.io/container: localhost/something spec: containers: - name: container image: scratch 07070100000026000081A400000000000000000000000166C635DC00000138000000000000000000000000000000000000004C00000000kubeaudit-0.22.2/auditors/apparmor/fixtures/apparmor-invalid-annotation.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-enabled annotations: container.apparmor.security.beta.kubernetes.io/container: runtime/default container.apparmor.security.beta.kubernetes.io/container2: runtime/default spec: containers: - name: container image: scratch 07070100000027000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001F00000000kubeaudit-0.22.2/auditors/asat07070100000028000081A400000000000000000000000166C635DC00000F0F000000000000000000000000000000000000002700000000kubeaudit-0.22.2/auditors/asat/asat.gopackage asat import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "asat" const ( // AutomountServiceAccountTokenDeprecated occurs when the deprecated serviceAccount field is non-empty AutomountServiceAccountTokenDeprecated = "AutomountServiceAccountTokenDeprecated" // AutomountServiceAccountTokenTrueAndDefaultSA occurs when automountServiceAccountToken is either not set // (which defaults to true) or explicitly set to true, and serviceAccountName is either not set or set to "default" AutomountServiceAccountTokenTrueAndDefaultSA = "AutomountServiceAccountTokenTrueAndDefaultSA" ) const OverrideLabel = "allow-automount-service-account-token" // AutomountServiceAccountToken implements Auditable type AutomountServiceAccountToken struct{} func New() *AutomountServiceAccountToken { return &AutomountServiceAccountToken{} } // Audit checks that the deprecated serviceAccount field is not used and that the default service account is not // being automatically mounted func (a *AutomountServiceAccountToken) Audit(resource k8s.Resource, resources []k8s.Resource) ([]*kubeaudit.AuditResult, error) { auditResult := auditResource(resource, resources) auditResult = override.ApplyOverride(auditResult, Name, "", resource, OverrideLabel) if auditResult != nil { return []*kubeaudit.AuditResult{auditResult}, nil } return nil, nil } func auditResource(resource k8s.Resource, resources []k8s.Resource) *kubeaudit.AuditResult { podSpec := k8s.GetPodSpec(resource) if podSpec == nil { return nil } if isDeprecatedServiceAccountName(podSpec) && !hasServiceAccountName(podSpec) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: AutomountServiceAccountTokenDeprecated, Severity: kubeaudit.Warn, Message: "serviceAccount is a deprecated alias for serviceAccountName. serviceAccountName should be used instead.", PendingFix: &fixDeprecatedServiceAccountName{ podSpec: podSpec, }, Metadata: kubeaudit.Metadata{ "DeprecatedServiceAccount": podSpec.DeprecatedServiceAccount, }, } } defaultServiceAccount := getDefaultServiceAccount(resources) if usesDefaultServiceAccount(podSpec) && isAutomountTokenTrue(podSpec, defaultServiceAccount) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: AutomountServiceAccountTokenTrueAndDefaultSA, Severity: kubeaudit.Error, Message: "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used.", PendingFix: &fixDefaultServiceAccountWithAutomountToken{ podSpec: podSpec, defaultServiceAccount: defaultServiceAccount, }, } } return nil } func isDeprecatedServiceAccountName(podSpec *k8s.PodSpecV1) bool { return podSpec.DeprecatedServiceAccount != "" } func hasServiceAccountName(podSpec *k8s.PodSpecV1) bool { return podSpec.ServiceAccountName != "" } func isAutomountTokenTrue(podSpec *k8s.PodSpecV1, defaultServiceAccount *k8s.ServiceAccountV1) bool { if podSpec.AutomountServiceAccountToken != nil { return *podSpec.AutomountServiceAccountToken } return defaultServiceAccount == nil || defaultServiceAccount.AutomountServiceAccountToken == nil || *defaultServiceAccount.AutomountServiceAccountToken } func usesDefaultServiceAccount(podSpec *k8s.PodSpecV1) bool { return podSpec.ServiceAccountName == "" || podSpec.ServiceAccountName == "default" } func getDefaultServiceAccount(resources []k8s.Resource) (serviceAccount *k8s.ServiceAccountV1) { for _, resource := range resources { serviceAccount, ok := resource.(*k8s.ServiceAccountV1) if ok && (k8s.GetObjectMeta(serviceAccount).GetName() == "default") { return serviceAccount } } return } 07070100000029000081A400000000000000000000000166C635DC0000079C000000000000000000000000000000000000002C00000000kubeaudit-0.22.2/auditors/asat/asat_test.gopackage asat import ( "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditAutomountServiceAccountToken(t *testing.T) { cases := []struct { file string expectedErrors []string testLocalMode bool }{ // When this yaml is applied into the cluster, both the deprecated and new service account fields are populated // with the service account value, so there is no error in local mode {"service-account-token-deprecated.yml", []string{AutomountServiceAccountTokenDeprecated}, false}, {"service-account-token-true-and-no-name.yml", []string{AutomountServiceAccountTokenTrueAndDefaultSA}, true}, {"service-account-token-nil-and-no-name.yml", []string{AutomountServiceAccountTokenTrueAndDefaultSA}, true}, {"service-account-token-true-allowed.yml", []string{ override.GetOverriddenResultName(AutomountServiceAccountTokenTrueAndDefaultSA)}, true, }, {"service-account-token-true-and-default-name.yml", []string{AutomountServiceAccountTokenTrueAndDefaultSA}, true}, {"service-account-token-false.yml", []string{}, true}, {"service-account-token-redundant-override.yml", []string{kubeaudit.RedundantAuditorOverride}, true}, {"service-account-token-nil-and-no-name-and-default-sa.yml", []string{}, true}, {"service-account-token-true-and-default-sa.yml", []string{AutomountServiceAccountTokenTrueAndDefaultSA}, true}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, fixtureDir, tc.file, New(), tc.expectedErrors) if tc.testLocalMode { test.AuditLocal(t, fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) } }) } } 0707010000002A000081A400000000000000000000000166C635DC00000627000000000000000000000000000000000000002600000000kubeaudit-0.22.2/auditors/asat/fix.gopackage asat import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" ) type fixDeprecatedServiceAccountName struct { podSpec *k8s.PodSpecV1 } func (f *fixDeprecatedServiceAccountName) Plan() string { return fmt.Sprintf("Set serviceAccountName to '%s' and set serviceAccount to '' in PodSpec", f.podSpec.DeprecatedServiceAccount) } func (f *fixDeprecatedServiceAccountName) Apply(resource k8s.Resource) []k8s.Resource { f.podSpec.ServiceAccountName = f.podSpec.DeprecatedServiceAccount f.podSpec.DeprecatedServiceAccount = "" return nil } type fixDefaultServiceAccountWithAutomountToken struct { podSpec *k8s.PodSpecV1 defaultServiceAccount *k8s.ServiceAccountV1 } func (f *fixDefaultServiceAccountWithAutomountToken) Plan() string { if f.defaultServiceAccount != nil { plan := "Set automountServiceAccountToken to 'false' in ServiceAccount" if f.podSpec.AutomountServiceAccountToken != nil && *(f.podSpec.AutomountServiceAccountToken) { plan += " and set automountServiceAccountToken to 'nil' in PodSpec" } return plan } return "Set automountServiceAccountToken to 'false' in PodSpec" } func (f *fixDefaultServiceAccountWithAutomountToken) Apply(resource k8s.Resource) []k8s.Resource { if f.defaultServiceAccount != nil { f.defaultServiceAccount.AutomountServiceAccountToken = k8s.NewFalse() if (f.podSpec.AutomountServiceAccountToken != nil) && *(f.podSpec.AutomountServiceAccountToken) { f.podSpec.AutomountServiceAccountToken = nil } } else { f.podSpec.AutomountServiceAccountToken = k8s.NewFalse() } return nil } 0707010000002B000081A400000000000000000000000166C635DC00000930000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/asat/fix_test.gopackage asat import ( "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" ) func TestFixAutomountServiceAccountToken(t *testing.T) { cases := []struct { file string expectedDeprecatedServiceAccount string expectedServiceAccountName string expectedAutomountToken *bool }{ {"service-account-token-deprecated.yml", "", "deprecated", nil}, {"service-account-token-nil-and-no-name.yml", "", "", k8s.NewFalse()}, {"service-account-token-redundant-override.yml", "", "", k8s.NewFalse()}, {"service-account-token-true-allowed.yml", "", "", k8s.NewTrue()}, {"service-account-token-true-and-default-name.yml", "", "default", k8s.NewFalse()}, {"service-account-token-true-and-no-name.yml", "", "", k8s.NewFalse()}, {"service-account-token-false.yml", "", "", k8s.NewFalse()}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, tc.file, New()) for _, resource := range resources { podSpec := k8s.GetPodSpec(resource) assert.Equal(t, tc.expectedDeprecatedServiceAccount, podSpec.DeprecatedServiceAccount) assert.Equal(t, tc.expectedServiceAccountName, podSpec.ServiceAccountName) if tc.expectedAutomountToken == nil { assert.Nil(t, podSpec.AutomountServiceAccountToken) } else { assert.Equal(t, *tc.expectedAutomountToken, *podSpec.AutomountServiceAccountToken) } } }) } // Test that if a default ServiceAccount was found, its 'automountServiceAccountToken' is set to false // instead of on the PodSpec files := []string{ "service-account-token-nil-and-no-name-and-default-sa.yml", "service-account-token-true-and-default-sa.yml", } for _, file := range files { t.Run(file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, file, New()) for _, resource := range resources { if serviceAccount, ok := resource.(*k8s.ServiceAccountV1); ok { assert.Equal(t, serviceAccount.AutomountServiceAccountToken, k8s.NewFalse()) continue } podSpec := k8s.GetPodSpec(resource) assert.Equal(t, "", podSpec.DeprecatedServiceAccount) assert.Equal(t, "", podSpec.ServiceAccountName) assert.Nil(t, podSpec.AutomountServiceAccountToken) } }) } } 0707010000002C000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002800000000kubeaudit-0.22.2/auditors/asat/fixtures0707010000002D000081A400000000000000000000000166C635DC0000014F000000000000000000000000000000000000004D00000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-deprecated.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-deprecated spec: template: metadata: labels: name: replicationcontroller spec: serviceAccount: deprecated containers: - name: replicationcontroller image: scratch 0707010000002E000081A400000000000000000000000166C635DC00000147000000000000000000000000000000000000004800000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-false.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-false spec: template: metadata: labels: name: replicationcontroller spec: automountServiceAccountToken: false containers: - name: container image: scratch 0707010000002F000081A400000000000000000000000166C635DC000001AA000000000000000000000000000000000000006100000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-nil-and-no-name-and-default-sa.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-nil-and-no-name-and-default-sa spec: template: metadata: labels: name: replicationcontroller spec: containers: - name: replicationcontroller image: scratch --- apiVersion: v1 kind: ServiceAccount metadata: name: default automountServiceAccountToken: false 07070100000030000081A400000000000000000000000166C635DC00000133000000000000000000000000000000000000005200000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-nil-and-no-name.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-nil-and-no-name spec: template: metadata: labels: name: replicationcontroller spec: containers: - name: replicationcontroller image: scratch 07070100000031000081A400000000000000000000000166C635DC0000019D000000000000000000000000000000000000005500000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-redundant-override.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-redundant-override spec: template: metadata: labels: name: replicationcontroller kubeaudit.io/allow-automount-service-account-token: "SomeReason" spec: automountServiceAccountToken: false containers: - name: container image: scratch 07070100000032000081A400000000000000000000000166C635DC00000196000000000000000000000000000000000000004F00000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-true-allowed.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-true-allowed spec: template: metadata: labels: name: replicationcontroller kubeaudit.io/allow-automount-service-account-token: "SomeReason" spec: automountServiceAccountToken: true containers: - name: container image: scratch 07070100000033000081A400000000000000000000000166C635DC00000178000000000000000000000000000000000000005800000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-true-and-default-name.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-true-and-default-name spec: template: metadata: labels: name: replicationcontroller spec: automountServiceAccountToken: true serviceAccountName: default containers: - name: container image: scratch 07070100000034000081A400000000000000000000000166C635DC000001BC000000000000000000000000000000000000005600000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-true-and-default-sa.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-true-and-default-sa spec: template: metadata: labels: name: replicationcontroller spec: automountServiceAccountToken: true containers: - name: container image: scratch --- apiVersion: v1 kind: ServiceAccount metadata: name: default automountServiceAccountToken: false 07070100000035000081A400000000000000000000000166C635DC0000015D000000000000000000000000000000000000005300000000kubeaudit-0.22.2/auditors/asat/fixtures/service-account-token-true-and-no-name.ymlapiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-true-and-no-name spec: template: metadata: labels: name: replicationcontroller spec: automountServiceAccountToken: true containers: - name: replicationcontroller image: scratch 07070100000036000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002700000000kubeaudit-0.22.2/auditors/capabilities07070100000037000081A400000000000000000000000166C635DC000010C2000000000000000000000000000000000000003700000000kubeaudit-0.22.2/auditors/capabilities/capabilities.gopackage capabilities import ( "fmt" "strings" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "capabilities" const ( // CapabilityAdded occurs when a capability is in the capability add list of a container's security context CapabilityAdded = "CapabilityAdded" // CapabilityShouldDropAll occurs when there's a drop list instead of having drop "ALL" CapabilityShouldDropAll = "CapabilityShouldDropAll" // CapabilityOrSecurityContextMissing occurs when either the Security Context or Capabilities are not specified CapabilityOrSecurityContextMissing = "CapabilityOrSecurityContextMissing" ) const overrideLabelPrefix = "allow-capability-" var DefaultDropList = []string{"ALL"} var DefaultAllowAddList = []string{""} // Capabilities implements Auditable type Capabilities struct { allowAddList []string } func New(config Config) *Capabilities { return &Capabilities{ allowAddList: config.GetAllowAddList(), } } // Audit checks that bad capabilities are dropped with ALL and no capabilities are added func (a *Capabilities) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult for _, container := range k8s.GetContainers(resource) { auditResult := auditContainerForDropAll(container) if auditResult != nil { auditResults = append(auditResults, auditResult) } for _, capability := range uniqueCapabilities(container) { for _, auditResult := range auditContainer(container, capability, a.allowAddList) { auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, getOverrideLabel(capability)) if auditResult != nil { auditResults = append(auditResults, auditResult) } } } } return auditResults, nil } func getOverrideLabel(capability string) string { return overrideLabelPrefix + strings.Replace(strings.ToLower(capability), "_", "-", -1) } func auditContainer(container *k8s.ContainerV1, capability string, allowAddList []string) []*kubeaudit.AuditResult { var auditResults []*kubeaudit.AuditResult if isCapabilityInArray(capability, allowAddList) { return auditResults } if SecurityContextOrCapabilities(container) { if IsCapabilityInAddList(container, capability) { message := fmt.Sprintf("Capability \"%s\" added. It should be removed from the capability add list. If you need this capability, add an override label such as '%s: SomeReason'.", capability, override.GetContainerOverrideLabel(container.Name, getOverrideLabel(capability))) auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: CapabilityAdded, Severity: kubeaudit.Error, Message: message, PendingFix: &fixCapabilityAdded{ container: container, capability: capability, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, "Metadata": capability, }, } auditResults = append(auditResults, auditResult) } } // We need the audit result to be nil for ApplyOverride to check for RedundantAuditorOverride errors if len(auditResults) == 0 { return []*kubeaudit.AuditResult{nil} } return auditResults } func auditContainerForDropAll(container *k8s.ContainerV1) *kubeaudit.AuditResult { if !SecurityContextOrCapabilities(container) { message := "Security Context not set. The Security Context should be specified and all Capabilities should be dropped by setting the Drop list to ALL." return &kubeaudit.AuditResult{ Auditor: Name, Rule: CapabilityOrSecurityContextMissing, Severity: kubeaudit.Error, Message: message, PendingFix: &fixMissingSecurityContextOrCapability{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if !IsDropAll(container) { message := "Capability Drop list should be set to ALL. Add the specific ones you need to the Add list and set an override label." return &kubeaudit.AuditResult{ Auditor: Name, Rule: CapabilityShouldDropAll, Severity: kubeaudit.Error, Message: message, PendingFix: &fixCapabilityNotDroppedAll{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } return nil } 07070100000038000081A400000000000000000000000166C635DC0000079B000000000000000000000000000000000000003C00000000kubeaudit-0.22.2/auditors/capabilities/capabilities_test.gopackage capabilities import ( "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditCapabilities(t *testing.T) { cases := []struct { file string fixtureDir string expectedErrors []string }{ {"capabilities-nil.yml", fixtureDir, []string{CapabilityOrSecurityContextMissing}}, {"capabilities-added.yml", fixtureDir, []string{CapabilityAdded}}, {"capabilities-added-not-dropped.yml", fixtureDir, []string{CapabilityAdded, CapabilityShouldDropAll}}, {"capabilities-some-allowed.yml", fixtureDir, []string{ override.GetOverriddenResultName(CapabilityAdded), CapabilityAdded, }}, {"capabilities-some-dropped.yml", fixtureDir, []string{CapabilityShouldDropAll}}, {"capabilities-dropped-all.yml", fixtureDir, []string{}}, {"capabilities-some-allowed-multi-containers-all-labels.yml", fixtureDir, []string{ CapabilityAdded, CapabilityShouldDropAll, override.GetOverriddenResultName(CapabilityAdded), }}, {"capabilities-some-allowed-multi-containers-some-labels.yml", fixtureDir, []string{ CapabilityAdded, CapabilityShouldDropAll, override.GetOverriddenResultName(CapabilityAdded), }}, {"capabilities-some-allowed-multi-containers-mix-labels.yml", fixtureDir, []string{ CapabilityAdded, CapabilityShouldDropAll, override.GetOverriddenResultName(CapabilityAdded), }}, {"capabilities-some-allowed-multi-containers-mix-old-labels.yml", fixtureDir, []string{ CapabilityAdded, CapabilityShouldDropAll, override.GetOverriddenResultName(CapabilityAdded), }}, } for _, tc := range cases { tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, tc.fixtureDir, tc.file, New(Config{}), tc.expectedErrors) test.AuditLocal(t, tc.fixtureDir, tc.file, New(Config{}), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 07070100000039000081A400000000000000000000000166C635DC00000102000000000000000000000000000000000000003100000000kubeaudit-0.22.2/auditors/capabilities/config.gopackage capabilities type Config struct { AllowAddList []string `yaml:"allowAddList"` } func (config *Config) GetAllowAddList() []string { if config == nil || len(config.AllowAddList) == 0 { return DefaultAllowAddList } return config.AllowAddList } 0707010000003A000081A400000000000000000000000166C635DC00000A7F000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/auditors/capabilities/fix.gopackage capabilities import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" v1 "k8s.io/api/core/v1" ) type fixCapabilityAdded struct { container *k8s.ContainerV1 capability string } func (f *fixCapabilityAdded) Plan() string { return fmt.Sprintf("Remove capability '%s' from the capability add list in the container SecurityContext for container %s", f.capability, f.container.Name) } func (f *fixCapabilityAdded) Apply(resource k8s.Resource) []k8s.Resource { removeCapabilityFromAddList(f.container, f.capability) return nil } type fixCapabilityNotDroppedAll struct { container *k8s.ContainerV1 capability string } func (f *fixCapabilityNotDroppedAll) Plan() string { return fmt.Sprintf("Remove '%s' capability from drop list in the container SecurityContext for container %s", f.capability, f.container.Name) } func (f *fixCapabilityNotDroppedAll) Apply(resource k8s.Resource) []k8s.Resource { dropCapabilityFromDropList(f.container, f.capability) return nil } func dropCapabilityFromDropList(container *k8s.ContainerV1, capability string) { if container.SecurityContext == nil { container.SecurityContext = &k8s.SecurityContextV1{} } if container.SecurityContext.Capabilities == nil { container.SecurityContext.Capabilities = &k8s.CapabilitiesV1{} } if container.SecurityContext.Capabilities.Drop == nil { container.SecurityContext.Capabilities.Drop = []k8s.CapabilityV1{} } container.SecurityContext.Capabilities.Drop = []v1.Capability{"ALL"} } func removeCapabilityFromAddList(container *k8s.ContainerV1, capability string) { added := container.SecurityContext.Capabilities.Add for i, add := range added { if string(add) == capability { added = append(added[:i], added[i+1:]...) break } } container.SecurityContext.Capabilities.Add = added } type fixMissingSecurityContextOrCapability struct { container *k8s.ContainerV1 } func (f *fixMissingSecurityContextOrCapability) Plan() string { return fmt.Sprintf("Adds security context and capabilities to %s. The capabilities Drop list is set to ALL.", f.container.Name) } func (f *fixMissingSecurityContextOrCapability) Apply(resource k8s.Resource) []k8s.Resource { setDropToAll(f.container) return nil } func setDropToAll(container *k8s.ContainerV1) { if container.SecurityContext == nil { container.SecurityContext = &k8s.SecurityContextV1{} } if container.SecurityContext.Capabilities == nil { container.SecurityContext.Capabilities = &k8s.CapabilitiesV1{} } if container.SecurityContext.Capabilities.Drop == nil { container.SecurityContext.Capabilities.Drop = []k8s.CapabilityV1{} } container.SecurityContext.Capabilities.Drop = []v1.Capability{"ALL"} } 0707010000003B000081A400000000000000000000000166C635DC0000159A000000000000000000000000000000000000003300000000kubeaudit-0.22.2/auditors/capabilities/fix_test.gopackage capabilities import ( "fmt" "testing" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" "github.com/stretchr/testify/assert" ) func TestFixCapabilities(t *testing.T) { customAllowAddList := []string{"apple", "banana"} cases := []struct { testName string overrides []string add []string expectedAdd []string drop []string expectedDrop []string }{ { testName: "Capabilities not set to ALL", overrides: []string{}, add: []string{}, expectedAdd: []string{}, drop: []string{"orange", "banana"}, expectedDrop: []string{"ALL"}, }, { testName: "Nothing to fix - no caps added and drop is set to all", overrides: []string{}, add: []string{}, expectedAdd: []string{}, drop: []string{"all"}, expectedDrop: []string{"all"}, }, { testName: "No capabilities specified", overrides: []string{}, add: []string{}, expectedAdd: []string{}, drop: []string{}, expectedDrop: []string{"ALL"}, }, { testName: "Capability Added with with override specified in AddList and 2 capabilities dropped", overrides: []string{}, add: []string{customAllowAddList[0], customAllowAddList[1], "orange"}, expectedAdd: []string{customAllowAddList[0], customAllowAddList[1]}, drop: []string{"pineapple", "pomegranate"}, expectedDrop: []string{"ALL"}, }, { testName: "CapabilityAdded - all", overrides: []string{}, add: []string{"ALL"}, expectedAdd: []string{}, drop: []string{"orange"}, expectedDrop: []string{"ALL"}, }, { testName: "CapabilityAdded", overrides: []string{}, add: []string{"orange"}, expectedAdd: []string{}, drop: []string{}, expectedDrop: []string{"ALL"}, }, { testName: "Pod override", overrides: []string{override.GetOverrideLabel(getOverrideLabel("orange"))}, add: []string{"orange"}, expectedAdd: []string{"orange"}, drop: []string{}, expectedDrop: []string{"ALL"}, }, { testName: "Container override", overrides: []string{override.GetContainerOverrideLabel("mycontainer", getOverrideLabel("pear"))}, add: []string{customAllowAddList[0], "pear", "orange"}, expectedAdd: []string{customAllowAddList[0], "pear"}, drop: []string{}, expectedDrop: []string{"ALL"}, }, { testName: "CapabilityAdded with 3 override labels", overrides: []string{override.GetContainerOverrideLabel("mycontainer", getOverrideLabel("blueberries")), override.GetContainerOverrideLabel("mycontainer", getOverrideLabel("strawberries")), override.GetContainerOverrideLabel("mycontainer", getOverrideLabel("raspberries"))}, add: []string{customAllowAddList[0], "blueberries", "raspberries", "strawberries", "orange"}, expectedAdd: []string{customAllowAddList[0], "blueberries", "raspberries", "strawberries"}, drop: []string{}, expectedDrop: []string{"ALL"}, }, } auditor := New(Config{AllowAddList: customAllowAddList}) for _, tc := range cases { t.Run(tc.testName, func(t *testing.T) { resource := newPod(tc.add, tc.drop, tc.overrides) auditResults, err := auditor.Audit(resource, nil) if !assert.Nil(t, err) { return } for _, auditResult := range auditResults { auditResult.Fix(resource) ok, plan := auditResult.FixPlan() if ok { fmt.Println(plan) } } capabilities := k8s.GetContainers(resource)[0].SecurityContext.Capabilities assertCapabilitiesEqual(t, capabilities.Add, tc.expectedAdd) assertCapabilitiesEqual(t, capabilities.Drop, tc.expectedDrop) }) } t.Run("Nil security context", func(t *testing.T) { resource := &k8s.PodV1{ Spec: k8s.PodSpecV1{ Containers: []k8s.ContainerV1{{}}, }, } auditResults, err := auditor.Audit(resource, nil) if !assert.Nil(t, err) { return } for _, auditResult := range auditResults { auditResult.Fix(resource) ok, plan := auditResult.FixPlan() if ok { fmt.Println(plan) } } capabilities := k8s.GetContainers(resource)[0].SecurityContext.Capabilities assertCapabilitiesEqual(t, capabilities.Drop, []string{"ALL"}) }) } func assertCapabilitiesEqual(t *testing.T, capabilities []k8s.CapabilityV1, expected []string) { assert := assert.New(t) if !assert.Equal(len(expected), len(capabilities)) { return } m := make(map[string]bool) for _, cap := range capabilities { m[string(cap)] = true } for _, cap := range expected { ok, val := m[cap] assert.True(ok) assert.True(val) } } func newPod(add, drop, overrides []string) k8s.Resource { pod := k8s.NewPod() container := k8s.ContainerV1{ Name: "mycontainer", SecurityContext: &k8s.SecurityContextV1{ Capabilities: &k8s.CapabilitiesV1{ Add: capabilitiesFromStringArray(add), Drop: capabilitiesFromStringArray(drop), }, }, } k8s.GetPodSpec(pod).Containers = []k8s.ContainerV1{container} overrideLabels := make(map[string]string) for _, override := range overrides { overrideLabels[override] = "SomeReason" } k8s.GetPodObjectMeta(pod).SetLabels(overrideLabels) return pod } func capabilitiesFromStringArray(arr []string) []k8s.CapabilityV1 { capabilities := make([]k8s.CapabilityV1, 0, len(arr)) for _, str := range arr { capabilities = append(capabilities, k8s.CapabilityV1(str)) } return capabilities } 0707010000003C000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000003000000000kubeaudit-0.22.2/auditors/capabilities/fixtures0707010000003D000081A400000000000000000000000166C635DC0000030A000000000000000000000000000000000000005300000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-added-not-dropped.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-added-not-dropped spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: capabilities: add: - AUDIT_WRITE drop: - CHOWN - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT 0707010000003E000081A400000000000000000000000166C635DC000001D2000000000000000000000000000000000000004700000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-added.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-added spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: capabilities: add: - NET_ADMIN - CHOWN drop: - ALL 0707010000003F000081A400000000000000000000000166C635DC00000191000000000000000000000000000000000000004D00000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-dropped-all.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-dropped-all spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: capabilities: drop: - all 07070100000040000081A400000000000000000000000166C635DC0000012A000000000000000000000000000000000000004500000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-nil.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-nil spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch 07070100000041000081A400000000000000000000000166C635DC000006AA000000000000000000000000000000000000006A00000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-some-allowed-multi-containers-all-labels.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-some-allowed-multi-containers-all-labels spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment container.kubeaudit.io/container1.allow-capability-chown: "SomeReason" container.kubeaudit.io/container1.allow-capability-sys-time: "SomeReason" container.kubeaudit.io/container2.allow-capability-chown: "SomeReason" container.kubeaudit.io/container2.allow-capability-sys-time: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT - name: container2 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT 07070100000042000081A400000000000000000000000166C635DC00000695000000000000000000000000000000000000006A00000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-some-allowed-multi-containers-mix-labels.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-some-allowed-multi-containers-mix-labels spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment kubeaudit.io/allow-capability-chown: "SomeReason" container.kubeaudit.io/container1.allow-capability-chown: "SomeReason" container.kubeaudit.io/container1.allow-capability-sys-time: "SomeReason" container.kubeaudit.io/container2.allow-capability-sys-time: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT - name: container2 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT 07070100000043000081A400000000000000000000000166C635DC00000716000000000000000000000000000000000000006E00000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-some-allowed-multi-containers-mix-old-labels.yml# this is to test backwards compatibility with old unregistered annotations (kubernetes.io) apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-some-allowed-multi-containers-mix-old-labels spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment audit.kubernetes.io/pod.allow-capability-chown: "SomeReason" container.audit.kubernetes.io/container1.allow-capability-chown: "SomeReason" container.audit.kubernetes.io/container1.allow-capability-sys-time: "SomeReason" container.audit.kubernetes.io/container2.allow-capability-sys-time: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT - name: container2 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT 07070100000044000081A400000000000000000000000166C635DC0000060A000000000000000000000000000000000000006B00000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-some-allowed-multi-containers-some-labels.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-some-allowed-multi-containers-some-labels spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment container.kubeaudit.io/container1.allow-capability-chown: "SomeReason" container.kubeaudit.io/container1.allow-capability-sys-time: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT - name: container2 image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE drop: - AUDIT_WRITE - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT 07070100000045000081A400000000000000000000000166C635DC0000026C000000000000000000000000000000000000004E00000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-some-allowed.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-some-allowed spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment kubeaudit.io/allow-capability-chown: "SomeReason" kubeaudit.io/allow-capability-sys-time: "SomeReason" spec: containers: - name: container image: scratch securityContext: capabilities: add: - SYS_TIME - SYS_MODULE - CHOWN drop: - ALL 07070100000046000081A400000000000000000000000166C635DC000002D4000000000000000000000000000000000000004E00000000kubeaudit-0.22.2/auditors/capabilities/fixtures/capabilities-some-dropped.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-some-dropped spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: capabilities: drop: - CHOWN - DAC_OVERRIDE - FOWNER - FSETID - KILL - MKNOD - NET_BIND_SERVICE - NET_RAW - SETFCAP - SETGID - SETUID - SETPCAP - SYS_CHROOT 07070100000047000081A400000000000000000000000166C635DC000006C2000000000000000000000000000000000000002F00000000kubeaudit-0.22.2/auditors/capabilities/util.gopackage capabilities import ( "sort" "strings" "github.com/Shopify/kubeaudit/pkg/k8s" ) // uniqueCapabilities creates an array of all unique capabilities in the custom drop list and container add list func uniqueCapabilities(container *k8s.ContainerV1) []string { if !SecurityContextOrCapabilities(container) { return DefaultDropList } m := make(map[string]bool) for _, cap := range container.SecurityContext.Capabilities.Add { m[string(cap)] = true } merged := make([]string, 0, len(m)) // "ALL" and "all" mean the same thing so we don't want to count both all := false for k := range m { if isCapabilityAll(k) { all = true continue } merged = append(merged, k) } if all { merged = append(merged, "ALL") } sort.Strings(merged) return merged } func isCapabilityAll(capability string) bool { return capability == "ALL" || capability == "all" } func isCapabilityInArray(capability string, capabilities []string) bool { if len(capabilities) == 0 { return false } for _, cap := range capabilities { if cap == capability { return true } } return false } func IsDropAll(container *k8s.ContainerV1) bool { for _, cap := range container.SecurityContext.Capabilities.Drop { if strings.ToUpper(string(cap)) == "ALL" { return true } } return false } func IsCapabilityInAddList(container *k8s.ContainerV1, capability string) bool { for _, cap := range container.SecurityContext.Capabilities.Add { if string(cap) == capability { return true } } return false } func SecurityContextOrCapabilities(container *k8s.ContainerV1) bool { if container.SecurityContext == nil || container.SecurityContext.Capabilities == nil { return false } return true } 07070100000048000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002900000000kubeaudit-0.22.2/auditors/deprecatedapis07070100000049000081A400000000000000000000000166C635DC000003FE000000000000000000000000000000000000003300000000kubeaudit-0.22.2/auditors/deprecatedapis/config.gopackage deprecatedapis import ( "fmt" "regexp" "strconv" ) type Config struct { CurrentVersion string `yaml:"currentVersion"` TargetedVersion string `yaml:"targetedVersion"` } type Version struct { Major int Minor int } func (config *Config) GetCurrentVersion() (*Version, error) { if config == nil { return nil, nil } return toMajorMinor(config.CurrentVersion) } func (config *Config) GetTargetedVersion() (*Version, error) { if config == nil { return nil, nil } return toMajorMinor(config.TargetedVersion) } func toMajorMinor(version string) (*Version, error) { if len(version) == 0 { return nil, nil } re := regexp.MustCompile(`^(\d{1,2})\.(\d{1,2})$`) if !re.MatchString(version) { return nil, fmt.Errorf("error parsing version: %s", version) } major, err := strconv.Atoi(re.FindStringSubmatch(version)[1]) if err != nil { return nil, err } minor, err := strconv.Atoi(re.FindStringSubmatch(version)[2]) if err != nil { return nil, err } return &Version{major, minor}, nil } 0707010000004A000081A400000000000000000000000166C635DC00001742000000000000000000000000000000000000003C00000000kubeaudit-0.22.2/auditors/deprecatedapis/depreceatedapis.gopackage deprecatedapis import ( "fmt" "strconv" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/pkg/k8s" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) const Name = "deprecatedapis" const ( // DeprecatedAPIUsed occurs when a deprecated resource type version is used DeprecatedAPIUsed = "DeprecatedAPIUsed" ) // DeprecatedAPIs implements Auditable type DeprecatedAPIs struct { CurrentVersion *Version TargetedVersion *Version } func New(config Config) (*DeprecatedAPIs, error) { currentVersion, err := config.GetCurrentVersion() if err != nil { return nil, fmt.Errorf("error creating DeprecatedAPIs auditor: %w", err) } targetedVersion, err := config.GetTargetedVersion() if err != nil { return nil, fmt.Errorf("error creating DeprecatedAPIs auditor: %w", err) } return &DeprecatedAPIs{ CurrentVersion: currentVersion, TargetedVersion: targetedVersion, }, nil } // APILifecycleDeprecated is a generated function on the available APIs, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. // https://github.com/kubernetes/code-generator/blob/v0.24.1/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go#L475-L479 type apiLifecycleDeprecated interface { APILifecycleDeprecated() (major, minor int) } // APILifecycleRemoved is a generated function on the available APIs, returning the release in which the API is no longer served as int versions of major and minor for comparison. // https://github.com/kubernetes/code-generator/blob/v0.24.1/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go#L491-L495 type apiLifecycleRemoved interface { APILifecycleRemoved() (major, minor int) } // APILifecycleReplacement is a generated function on the available APIs, returning the group, version, and kind that should be used instead of this deprecated type. // https://github.com/kubernetes/code-generator/blob/v0.24.1/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go#L482-L487 type apiLifecycleReplacement interface { APILifecycleReplacement() schema.GroupVersionKind } // APILifecycleIntroduced is a generated function on the available APIs, returning the release in which the API struct was introduced as int versions of major and minor for comparison. // https://github.com/kubernetes/code-generator/blob/v0.24.1/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go#L467-L473 type apiLifecycleIntroduced interface { APILifecycleIntroduced() (major, minor int) } // Audit checks that the resource API version is not deprecated func (deprecatedAPIs *DeprecatedAPIs) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult lastApplied, ok := k8s.GetAnnotations(resource)[v1.LastAppliedConfigAnnotation] if ok && len(lastApplied) > 0 { resource, _ = k8sinternal.DecodeResource([]byte(lastApplied)) } deprecated, isDeprecated := resource.(apiLifecycleDeprecated) if isDeprecated { deprecatedMajor, deprecatedMinor := deprecated.APILifecycleDeprecated() if deprecatedMajor == 0 && deprecatedMinor == 0 { return nil, fmt.Errorf("version not found %s (%d.%d)", deprecated, deprecatedMajor, deprecatedMinor) } else { severity := kubeaudit.Warn metadata := kubeaudit.Metadata{ "DeprecatedMajor": strconv.Itoa(deprecatedMajor), "DeprecatedMinor": strconv.Itoa(deprecatedMinor), } if deprecatedAPIs.CurrentVersion != nil && (deprecatedAPIs.CurrentVersion.Major < deprecatedMajor || deprecatedAPIs.CurrentVersion.Major == deprecatedMajor && deprecatedAPIs.CurrentVersion.Minor < deprecatedMinor) { severity = kubeaudit.Info } gvk := resource.GetObjectKind().GroupVersionKind() if gvk.Empty() { return nil, fmt.Errorf("GroupVersionKind not found %s", resource) } else { deprecationMessage := fmt.Sprintf("%s %s is deprecated in v%d.%d+", gvk.GroupVersion().String(), gvk.Kind, deprecatedMajor, deprecatedMinor) if removed, hasRemovalInfo := resource.(apiLifecycleRemoved); hasRemovalInfo { removedMajor, removedMinor := removed.APILifecycleRemoved() if removedMajor != 0 || removedMinor != 0 { deprecationMessage = deprecationMessage + fmt.Sprintf(", unavailable in v%d.%d+", removedMajor, removedMinor) metadata["RemovedMajor"] = strconv.Itoa(removedMajor) metadata["RemovedMinor"] = strconv.Itoa(removedMinor) } if deprecatedAPIs.TargetedVersion != nil && deprecatedAPIs.TargetedVersion.Major >= removedMajor && deprecatedAPIs.TargetedVersion.Minor >= removedMinor { severity = kubeaudit.Error } } if introduced, hasIntroduced := resource.(apiLifecycleIntroduced); hasIntroduced { introducedMajor, introducedMinor := introduced.APILifecycleIntroduced() if introducedMajor != 0 || introducedMinor != 0 { deprecationMessage = deprecationMessage + fmt.Sprintf(", introduced in v%d.%d+", introducedMajor, introducedMinor) metadata["IntroducedMajor"] = strconv.Itoa(introducedMajor) metadata["IntroducedMinor"] = strconv.Itoa(introducedMinor) } } if replaced, hasReplacement := resource.(apiLifecycleReplacement); hasReplacement { replacement := replaced.APILifecycleReplacement() if !replacement.Empty() { deprecationMessage = deprecationMessage + fmt.Sprintf("; use %s %s", replacement.GroupVersion().String(), replacement.Kind) metadata["ReplacementGroup"] = replacement.GroupVersion().String() metadata["ReplacementKind"] = replacement.Kind } } auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: DeprecatedAPIUsed, Severity: severity, Message: deprecationMessage, Metadata: metadata, } auditResults = append(auditResults, auditResult) } } } return auditResults, nil } 0707010000004B000081A400000000000000000000000166C635DC00000C58000000000000000000000000000000000000004100000000kubeaudit-0.22.2/auditors/deprecatedapis/depreceatedapis_test.gopackage deprecatedapis import ( "fmt" "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const fixtureDir = "fixtures" func TestAuditDeprecatedAPIs(t *testing.T) { cases := []struct { file string currentVersion string targetedVersion string expectedSeverity kubeaudit.SeverityLevel }{ {"cronjob.yml", "", "", kubeaudit.Warn}, // Warn is the serverity by default {"cronjob.yml", "1.20", "1.21", kubeaudit.Info}, // Info, not yet deprecated in the current version {"cronjob.yml", "1.21", "1.22", kubeaudit.Warn}, // Warn, deprecated in the current version {"cronjob.yml", "1.22", "1.25", kubeaudit.Error}, // Error, not available in the targeted version {"cronjob.yml", "1.20", "1.25", kubeaudit.Error}, // Error, not yet deprecated in the current version but not available in the targeted version {"cronjob.yml", "1.20", "", kubeaudit.Info}, // Info, not yet deprecated in the current version and no targeted version defined {"cronjob.yml", "1.21", "", kubeaudit.Warn}, // Warn, deprecated in the current version {"cronjob.yml", "", "1.20", kubeaudit.Warn}, // Warn is the serverity by default if no current version {"cronjob.yml", "", "1.25", kubeaudit.Error}, // Error, not available in the targeted version } message := "batch/v1beta1 CronJob is deprecated in v1.21+, unavailable in v1.25+, introduced in v1.8+; use batch/v1 CronJob" metadata := kubeaudit.Metadata{ "DeprecatedMajor": "1", "DeprecatedMinor": "21", "RemovedMajor": "1", "RemovedMinor": "25", "IntroducedMajor": "1", "IntroducedMinor": "8", "ReplacementGroup": "batch/v1", "ReplacementKind": "CronJob", } for i, tc := range cases { // These lines are needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc i := i t.Run(tc.file+"-"+tc.currentVersion+"-"+tc.targetedVersion, func(t *testing.T) { t.Parallel() auditor, err := New(Config{CurrentVersion: tc.currentVersion, TargetedVersion: tc.targetedVersion}) require.Nil(t, err) report := test.AuditManifest(t, fixtureDir, tc.file, auditor, []string{DeprecatedAPIUsed}) assertReport(t, report, tc.expectedSeverity, message, metadata) report = test.AuditLocal(t, fixtureDir, tc.file, auditor, fmt.Sprintf("%s-%d", strings.Split(tc.file, ".")[0], i), []string{DeprecatedAPIUsed}) if report != nil { assertReport(t, report, tc.expectedSeverity, message, metadata) } }) } } func assertReport(t *testing.T, report *kubeaudit.Report, expectedSeverity kubeaudit.SeverityLevel, message string, metadata map[string]string) { assert.Equal(t, 1, len(report.Results())) for _, result := range report.Results() { assert.Equal(t, 1, len(result.GetAuditResults())) for _, auditResult := range result.GetAuditResults() { require.Equal(t, expectedSeverity, auditResult.Severity) require.Equal(t, message, auditResult.Message) require.Equal(t, metadata, auditResult.Metadata) } } } 0707010000004C000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000003200000000kubeaudit-0.22.2/auditors/deprecatedapis/fixtures0707010000004D000081A400000000000000000000000166C635DC000001A1000000000000000000000000000000000000003E00000000kubeaudit-0.22.2/auditors/deprecatedapis/fixtures/cronjob.ymlapiVersion: batch/v1beta1 kind: CronJob metadata: name: hello spec: schedule: "* * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox imagePullPolicy: IfNotPresent command: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure0707010000004E000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002100000000kubeaudit-0.22.2/auditors/hostns0707010000004F000081A400000000000000000000000166C635DC00000355000000000000000000000000000000000000002800000000kubeaudit-0.22.2/auditors/hostns/fix.gopackage hostns import "github.com/Shopify/kubeaudit/pkg/k8s" type fixHostNetworkTrue struct { podSpec *k8s.PodSpecV1 } func (f *fixHostNetworkTrue) Plan() string { return "Set hostNetwork to 'false' in PodSpec" } func (f *fixHostNetworkTrue) Apply(resource k8s.Resource) []k8s.Resource { f.podSpec.HostNetwork = false return nil } type fixHostIPCTrue struct { podSpec *k8s.PodSpecV1 } func (f *fixHostIPCTrue) Plan() string { return "Set hostIPC to 'false' in PodSpec" } func (f *fixHostIPCTrue) Apply(resource k8s.Resource) []k8s.Resource { f.podSpec.HostIPC = false return nil } type fixHostPIDTrue struct { podSpec *k8s.PodSpecV1 } func (f *fixHostPIDTrue) Plan() string { return "Set hostPID to 'false' in PodSpec" } func (f *fixHostPIDTrue) Apply(resource k8s.Resource) []k8s.Resource { f.podSpec.HostPID = false return nil } 07070100000050000081A400000000000000000000000166C635DC000004CA000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/auditors/hostns/fix_test.gopackage hostns import ( "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" ) func TestFixHostNamespaces(t *testing.T) { cases := []struct { file string expectedHostNetwork bool expectedHostIPC bool expectedHostPID bool }{ {"host-network-true.yml", false, false, false}, {"host-ipc-true.yml", false, false, false}, {"host-pid-true.yml", false, false, false}, {"host-network-true-allowed.yml", true, false, false}, {"host-ipc-true-allowed.yml", false, true, false}, {"host-pid-true-allowed.yml", false, false, true}, {"namespaces-redundant-override.yml", false, false, false}, {"namespaces-all-true.yml", false, false, false}, {"namespaces-all-true-allowed.yml", true, true, true}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, tc.file, New()) for _, resource := range resources { podSpec := k8s.GetPodSpec(resource) assert.Equal(t, tc.expectedHostNetwork, podSpec.HostNetwork) assert.Equal(t, tc.expectedHostIPC, podSpec.HostIPC) assert.Equal(t, tc.expectedHostPID, podSpec.HostPID) } }) } } 07070100000051000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/auditors/hostns/fixtures07070100000052000081A400000000000000000000000166C635DC000000E3000000000000000000000000000000000000004400000000kubeaudit-0.22.2/auditors/hostns/fixtures/host-ipc-true-allowed.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: host-ipc-true-allowed labels: kubeaudit.io/allow-namespace-host-IPC: "SomeReason" spec: hostIPC: true containers: - name: container image: scratch 07070100000053000081A400000000000000000000000166C635DC00000099000000000000000000000000000000000000003C00000000kubeaudit-0.22.2/auditors/hostns/fixtures/host-ipc-true.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: host-ipc-true spec: hostIPC: true containers: - name: container image: scratch 07070100000054000081A400000000000000000000000166C635DC000000EF000000000000000000000000000000000000004800000000kubeaudit-0.22.2/auditors/hostns/fixtures/host-network-true-allowed.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: host-network-true-allowed labels: kubeaudit.io/allow-namespace-host-network: "SomeReason" spec: hostNetwork: true containers: - name: container image: scratch 07070100000055000081A400000000000000000000000166C635DC000000A1000000000000000000000000000000000000004000000000kubeaudit-0.22.2/auditors/hostns/fixtures/host-network-true.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: host-network-true spec: hostNetwork: true containers: - name: container image: scratch 07070100000056000081A400000000000000000000000166C635DC000000E3000000000000000000000000000000000000004400000000kubeaudit-0.22.2/auditors/hostns/fixtures/host-pid-true-allowed.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: host-pid-true-allowed labels: kubeaudit.io/allow-namespace-host-PID: "SomeReason" spec: hostPID: true containers: - name: container image: scratch 07070100000057000081A400000000000000000000000166C635DC00000099000000000000000000000000000000000000003C00000000kubeaudit-0.22.2/auditors/hostns/fixtures/host-pid-true.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: host-pid-true spec: hostPID: true containers: - name: container image: scratch 07070100000058000081A400000000000000000000000166C635DC00000181000000000000000000000000000000000000004A00000000kubeaudit-0.22.2/auditors/hostns/fixtures/namespaces-all-true-allowed.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: namespaces-all-true-allowed labels: kubeaudit.io/allow-namespace-host-network: "SomeReason" kubeaudit.io/allow-namespace-host-IPC: "SomeReason" kubeaudit.io/allow-namespace-host-PID: "SomeReason" spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000059000081A400000000000000000000000166C635DC000000C3000000000000000000000000000000000000004200000000kubeaudit-0.22.2/auditors/hostns/fixtures/namespaces-all-true.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: namespaces-all-true spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 0707010000005A000081A400000000000000000000000166C635DC000000F4000000000000000000000000000000000000004C00000000kubeaudit-0.22.2/auditors/hostns/fixtures/namespaces-redundant-override.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: namespaces-redundant-override labels: kubeaudit.io/allow-namespace-host-network: "SomeReason" spec: hostNetwork: false containers: - name: container image: scratch 0707010000005B000081A400000000000000000000000166C635DC00000CBA000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/hostns/hostns.gopackage hostns import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "hostns" const ( // NamespaceHostNetworkTrue occurs when hostNetwork is set to true in the container podspec NamespaceHostNetworkTrue = "NamespaceHostNetworkTrue" // NamespaceHostIPCTrue occurs when hostIPC is set to true in the container podspec NamespaceHostIPCTrue = "NamespaceHostIPCTrue" // NamespaceHostPIDTrue occurs when hostPID is set to true in the container podspec NamespaceHostPIDTrue = "NamespaceHostPIDTrue" ) // HostNamespaces implements Auditable type HostNamespaces struct{} func New() *HostNamespaces { return &HostNamespaces{} } const HostNetworkOverrideLabel = "allow-namespace-host-network" const HostIPCOverrideLabel = "allow-namespace-host-IPC" const HostPIDOverrideLabel = "allow-namespace-host-PID" // Audit checks that hostNetwork, hostIPC and hostPID are set to false in container podSpecs func (a *HostNamespaces) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult podSpec := k8s.GetPodSpec(resource) if podSpec == nil { return nil, nil } for _, check := range []struct { auditFunc func(*k8s.PodSpecV1) *kubeaudit.AuditResult overrideLabel string }{ {auditHostNetwork, HostNetworkOverrideLabel}, {auditHostIPC, HostIPCOverrideLabel}, {auditHostPID, HostPIDOverrideLabel}, } { auditResult := check.auditFunc(podSpec) auditResult = override.ApplyOverride(auditResult, Name, "", resource, check.overrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } } return auditResults, nil } func auditHostNetwork(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { if podSpec.HostNetwork { metadata := kubeaudit.Metadata{} if podSpec.Hostname != "" { metadata["PodHost"] = podSpec.Hostname } return &kubeaudit.AuditResult{ Auditor: Name, Rule: NamespaceHostNetworkTrue, Severity: kubeaudit.Error, Message: "hostNetwork is set to 'true' in PodSpec. It should be set to 'false'.", PendingFix: &fixHostNetworkTrue{ podSpec: podSpec, }, Metadata: metadata, } } return nil } func auditHostIPC(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { if podSpec.HostIPC { metadata := kubeaudit.Metadata{} if podSpec.Hostname != "" { metadata["PodHost"] = podSpec.Hostname } return &kubeaudit.AuditResult{ Auditor: Name, Rule: NamespaceHostIPCTrue, Severity: kubeaudit.Error, Message: "hostIPC is set to 'true' in PodSpec. It should be set to 'false'.", PendingFix: &fixHostIPCTrue{ podSpec: podSpec, }, Metadata: metadata, } } return nil } func auditHostPID(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { if podSpec.HostPID { metadata := kubeaudit.Metadata{} if podSpec.Hostname != "" { metadata["PodHost"] = podSpec.Hostname } return &kubeaudit.AuditResult{ Auditor: Name, Rule: NamespaceHostPIDTrue, Severity: kubeaudit.Error, Message: "hostPID is set to 'true' in PodSpec. It should be set to 'false'.", PendingFix: &fixHostPIDTrue{ podSpec: podSpec, }, Metadata: metadata, } } return nil } 0707010000005C000081A400000000000000000000000166C635DC00000681000000000000000000000000000000000000003000000000kubeaudit-0.22.2/auditors/hostns/hostns_test.gopackage hostns import ( "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditHostNamespaces(t *testing.T) { cases := []struct { file string expectedErrors []string }{ {"host-network-true.yml", []string{NamespaceHostNetworkTrue}}, {"host-ipc-true.yml", []string{NamespaceHostIPCTrue}}, {"host-pid-true.yml", []string{NamespaceHostPIDTrue}}, {"host-network-true-allowed.yml", []string{override.GetOverriddenResultName(NamespaceHostNetworkTrue)}}, {"host-ipc-true-allowed.yml", []string{override.GetOverriddenResultName(NamespaceHostIPCTrue)}}, {"host-pid-true-allowed.yml", []string{override.GetOverriddenResultName(NamespaceHostPIDTrue)}}, {"namespaces-redundant-override.yml", []string{kubeaudit.RedundantAuditorOverride}}, {"namespaces-all-true.yml", []string{NamespaceHostNetworkTrue, NamespaceHostIPCTrue, NamespaceHostPIDTrue}}, {"namespaces-all-true-allowed.yml", []string{ override.GetOverriddenResultName(NamespaceHostNetworkTrue), override.GetOverriddenResultName(NamespaceHostIPCTrue), override.GetOverriddenResultName(NamespaceHostPIDTrue), }}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, fixtureDir, tc.file, New(), tc.expectedErrors) test.AuditLocal(t, fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 0707010000005D000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002000000000kubeaudit-0.22.2/auditors/image0707010000005E000081A400000000000000000000000166C635DC000000A8000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/auditors/image/config.gopackage image type Config struct { Image string `yaml:"image"` } func (config *Config) GetImage() string { if config == nil { return "" } return config.Image } 0707010000005F000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002900000000kubeaudit-0.22.2/auditors/image/fixtures07070100000060000081A400000000000000000000000166C635DC000001C3000000000000000000000000000000000000003F00000000kubeaudit-0.22.2/auditors/image/fixtures/image-tag-missing.yml--- apiVersion: apps/v1 kind: Deployment metadata: name: deployment spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment annotations: container.apparmor.security.beta.kubernetes.io/container: runtime/default spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: container image: scratch 07070100000061000081A400000000000000000000000166C635DC00000111000000000000000000000000000000000000003F00000000kubeaudit-0.22.2/auditors/image/fixtures/image-tag-present.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: deployment image: scratch:1.5 07070100000062000081A400000000000000000000000166C635DC00000A94000000000000000000000000000000000000002900000000kubeaudit-0.22.2/auditors/image/image.gopackage image import ( "fmt" "strings" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" ) const Name = "image" const ( // ImageTagMissing occurs when the container image tag is missing ImageTagMissing = "ImageTagMissing" // ImageTagIncorrect occurs when the container image tag does not match the user-provided value ImageTagIncorrect = "ImageTagIncorrect" // ImageCorrect occurs when the container image tag is correct ImageCorrect = "ImageCorrect" ) // Image implements Auditable type Image struct { image string } func New(config Config) *Image { return &Image{ image: config.GetImage(), } } // Audit checks that the container image matches the provided image func (image *Image) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, image.image) if auditResult != nil { auditResults = append(auditResults, auditResult) } } return auditResults, nil } func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditResult { name, tag := splitImageString(image) containerName, containerTag := splitImageString(container.Image) if isImageTagMissing(containerTag) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: ImageTagMissing, Severity: kubeaudit.Warn, Message: "Image tag is missing.", Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if isImageTagIncorrect(name, tag, containerName, containerTag) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: ImageTagIncorrect, Severity: kubeaudit.Error, Message: fmt.Sprintf("Container tag is incorrect. It should be set to '%s'.", tag), Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if isImageCorrect(name, tag, containerName, containerTag) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: ImageCorrect, Severity: kubeaudit.Info, Message: "Image tag is correct", Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } return nil } func isImageTagMissing(tag string) bool { return tag == "" } func isImageTagIncorrect(name, tag, containerName, containerTag string) bool { return containerName == name && containerTag != tag } func isImageCorrect(name, tag, containerName, containerTag string) bool { return containerName == name && containerTag == tag } func splitImageString(image string) (name, tag string) { tokens := strings.Split(image, ":") if len(tokens) > 0 { name = tokens[0] } if len(tokens) > 1 { tag = tokens[1] } return } 07070100000063000081A400000000000000000000000166C635DC00000696000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/auditors/image/image_test.gopackage image import ( "fmt" "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/stretchr/testify/assert" ) const fixtureDir = "fixtures" func TestSplitImageString(t *testing.T) { cases := []struct { testName string image string expectedName string expectedTag string }{ {"Correct image and tag", "myimage:mytag", "myimage", "mytag"}, {"No tag", "myimage", "myimage", ""}, {"No image", ":mytag", "", "mytag"}, {"Empty string", "", "", ""}, } for _, tc := range cases { t.Run(tc.testName, func(t *testing.T) { image, tag := splitImageString(tc.image) assert.Equal(t, tc.expectedName, image) assert.Equal(t, tc.expectedTag, tag) }) } } func TestAuditImage(t *testing.T) { cases := []struct { file string image string expectedErrors []string }{ {"image-tag-missing.yml", "scratch:1.6", []string{ImageTagMissing}}, {"image-tag-missing.yml", "", []string{ImageTagMissing}}, {"image-tag-present.yml", "scratch:1.6", []string{ImageTagIncorrect}}, {"image-tag-present.yml", "", []string{}}, {"image-tag-present.yml", "scratch:1.5", []string{ImageCorrect}}, } for i, tc := range cases { // These lines are needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc i := i t.Run(tc.file+" "+tc.image, func(t *testing.T) { t.Parallel() test.AuditManifest(t, fixtureDir, tc.file, New(Config{Image: tc.image}), tc.expectedErrors) test.AuditLocal(t, fixtureDir, tc.file, New(Config{Image: tc.image}), fmt.Sprintf("%s%d", strings.Split(tc.file, ".")[0], i), tc.expectedErrors) }) } } 07070100000064000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002100000000kubeaudit-0.22.2/auditors/limits07070100000065000081A400000000000000000000000166C635DC0000036A000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/limits/config.gopackage limits import ( "fmt" k8sResource "k8s.io/apimachinery/pkg/api/resource" ) type Config struct { CPU string `yaml:"cpu"` Memory string `yaml:"memory"` } func (config *Config) GetCPU() (k8sResource.Quantity, error) { cpuArg := "" if config != nil { cpuArg = config.CPU } if cpuArg != "" { CPU, err := k8sResource.ParseQuantity(cpuArg) if err != nil { return CPU, fmt.Errorf("error parsing max CPU limit: %w", err) } return CPU, nil } return k8sResource.Quantity{}, nil } func (config *Config) GetMemory() (k8sResource.Quantity, error) { memoryArg := "" if config != nil { memoryArg = config.Memory } if memoryArg != "" { memory, err := k8sResource.ParseQuantity(memoryArg) if err != nil { return memory, fmt.Errorf("error parsing max memory limit: %w", err) } return memory, nil } return k8sResource.Quantity{}, nil } 07070100000066000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/auditors/limits/fixtures07070100000067000081A400000000000000000000000166C635DC0000006E000000000000000000000000000000000000004200000000kubeaudit-0.22.2/auditors/limits/fixtures/resources-limit-nil.ymlapiVersion: v1 kind: Pod metadata: name: pod spec: containers: - name: container image: scratch 07070100000068000081A400000000000000000000000166C635DC000000A7000000000000000000000000000000000000004500000000kubeaudit-0.22.2/auditors/limits/fixtures/resources-limit-no-cpu.ymlapiVersion: v1 kind: Pod metadata: name: pod spec: containers: - name: container image: scratch resources: limits: memory: 512Mi 07070100000069000081A400000000000000000000000166C635DC000000A2000000000000000000000000000000000000004800000000kubeaudit-0.22.2/auditors/limits/fixtures/resources-limit-no-memory.ymlapiVersion: v1 kind: Pod metadata: name: pod spec: containers: - name: container image: scratch resources: limits: cpu: "1" 0707010000006A000081A400000000000000000000000166C635DC000000F9000000000000000000000000000000000000003E00000000kubeaudit-0.22.2/auditors/limits/fixtures/resources-limit.ymlapiVersion: v1 kind: Pod metadata: name: pod spec: containers: - name: container image: scratch resources: limits: cpu: 750m memory: 512Mi requests: cpu: 500m memory: 256Mi 0707010000006B000081A400000000000000000000000166C635DC0000147E000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/limits/limits.gopackage limits import ( "fmt" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" v1 "k8s.io/api/core/v1" k8sResource "k8s.io/apimachinery/pkg/api/resource" ) const Name = "limits" const ( // LimitsNotSet occurs when there are no cpu and memory limits specified for a container LimitsNotSet = "LimitsNotSet" // LimitsCPUNotSet occurs when there is no cpu limit specified for a container LimitsCPUNotSet = "LimitsCPUNotSet" // LimitsMemoryNotSet occurs when there is no memory limit specified for a container LimitsMemoryNotSet = "LimitsMemoryNotSet" // LimitsCPUExceeded occurs when the CPU limit specified for a container is higher than the specified max CPU limit LimitsCPUExceeded = "LimitsCPUExceeded" // LimitsMemoryExceeded occurs when the memory limit specified for a container is higher than the specified max memory limit LimitsMemoryExceeded = "LimitsMemoryExceeded" ) // Limits implements Auditable type Limits struct { maxCPU k8sResource.Quantity maxMemory k8sResource.Quantity } func New(config Config) (*Limits, error) { maxCPU, err := config.GetCPU() if err != nil { return nil, fmt.Errorf("error creating Limits auditor: %w", err) } maxMemory, err := config.GetMemory() if err != nil { return nil, fmt.Errorf("error creating Limits auditor: %w", err) } return &Limits{ maxCPU: maxCPU, maxMemory: maxMemory, }, nil } // Audit checks that the container cpu and memory limits do not exceed specified limits func (limits *Limits) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult for _, container := range k8s.GetContainers(resource) { for _, auditResult := range limits.auditContainer(container) { if auditResult != nil { auditResults = append(auditResults, auditResult) } } } return auditResults, nil } func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults []*kubeaudit.AuditResult) { if isLimitsNil(container) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: LimitsNotSet, Severity: kubeaudit.Warn, Message: "Resource limits not set.", Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } return []*kubeaudit.AuditResult{auditResult} } containerLimits := getLimits(container) cpu := containerLimits.Cpu().String() memory := containerLimits.Memory().String() if isCPULimitUnset(container) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: LimitsCPUNotSet, Severity: kubeaudit.Warn, Message: "Resource CPU limit not set.", Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } auditResults = append(auditResults, auditResult) } else if exceedsCPULimit(container, limits) { maxCPU := limits.maxCPU.String() auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: LimitsCPUExceeded, Severity: kubeaudit.Warn, Message: fmt.Sprintf("CPU limit exceeded. It is set to '%s' which exceeds the max CPU limit of '%s'.", cpu, maxCPU), Metadata: kubeaudit.Metadata{ "Container": container.Name, "ContainerCpuLimit": cpu, "MaxCPU": maxCPU, }, } auditResults = append(auditResults, auditResult) } if isMemoryLimitUnset(container) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: LimitsMemoryNotSet, Severity: kubeaudit.Warn, Message: "Resource Memory limit not set.", Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } auditResults = append(auditResults, auditResult) } else if exceedsMemoryLimit(container, limits) { maxMemory := limits.maxMemory.String() auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: LimitsMemoryExceeded, Severity: kubeaudit.Warn, Message: fmt.Sprintf("Memory limit exceeded. It is set to '%s' which exceeds the max Memory limit of '%s'.", memory, maxMemory), Metadata: kubeaudit.Metadata{ "Container": container.Name, "ContainerMemoryLimit": memory, "MaxMemory": maxMemory, }, } auditResults = append(auditResults, auditResult) } return } func exceedsCPULimit(container *k8s.ContainerV1, limits *Limits) bool { containerLimits := getLimits(container) cpuLimit := containerLimits.Cpu().MilliValue() maxCPU := limits.maxCPU.MilliValue() return maxCPU > 0 && cpuLimit > maxCPU } func exceedsMemoryLimit(container *k8s.ContainerV1, limits *Limits) bool { containerLimits := getLimits(container) memoryLimit := containerLimits.Memory().Value() maxMemory := limits.maxMemory.Value() return maxMemory > 0 && memoryLimit > maxMemory } func isLimitsNil(container *k8s.ContainerV1) bool { return container.Resources.Limits == nil } func isCPULimitUnset(container *k8s.ContainerV1) bool { limits := getLimits(container) cpu := limits.Cpu() return cpu == nil || cpu.IsZero() } func isMemoryLimitUnset(container *k8s.ContainerV1) bool { limits := getLimits(container) memory := limits.Memory() return memory == nil || memory.IsZero() } func getLimits(container *k8s.ContainerV1) v1.ResourceList { if isLimitsNil(container) { return v1.ResourceList{} } return container.Resources.Limits } 0707010000006C000081A400000000000000000000000166C635DC00000692000000000000000000000000000000000000003000000000kubeaudit-0.22.2/auditors/limits/limits_test.gopackage limits import ( "fmt" "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/stretchr/testify/assert" ) const fixtureDir = "fixtures" func TestAuditLimits(t *testing.T) { cases := []struct { file string maxCPU string maxMemory string expectedErrors []string }{ {"resources-limit-nil.yml", "", "", []string{LimitsNotSet}}, {"resources-limit-no-cpu.yml", "", "", []string{LimitsCPUNotSet}}, {"resources-limit-no-memory.yml", "", "", []string{LimitsMemoryNotSet}}, {"resources-limit.yml", "", "", []string{}}, {"resources-limit.yml", "600m", "", []string{LimitsCPUExceeded}}, {"resources-limit.yml", "", "384", []string{LimitsMemoryExceeded}}, {"resources-limit.yml", "600m", "384", []string{LimitsCPUExceeded, LimitsMemoryExceeded}}, {"resources-limit.yml", "750m", "512Mi", []string{}}, } for i, tc := range cases { // These lines are needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc i := i t.Run(fmt.Sprintf("%s %s %s", tc.file, tc.maxCPU, tc.maxMemory), func(t *testing.T) { t.Parallel() auditor, err := New(Config{CPU: tc.maxCPU, Memory: tc.maxMemory}) assert.Nil(t, err) test.AuditManifest(t, fixtureDir, tc.file, auditor, tc.expectedErrors) test.AuditLocal(t, fixtureDir, tc.file, auditor, fmt.Sprintf("%s%d", strings.Split(tc.file, ".")[0], i), tc.expectedErrors) }) } t.Run("Bad arguments", func(t *testing.T) { _, err := New(Config{CPU: "badvalue", Memory: ""}) assert.NotNil(t, err) _, err = New(Config{CPU: "", Memory: "badvalue"}) assert.NotNil(t, err) }) } 0707010000006D000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002100000000kubeaudit-0.22.2/auditors/mounts0707010000006E000081A400000000000000000000000166C635DC00000106000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/mounts/config.gopackage mounts type Config struct { SensitivePaths []string `yaml:"denyPathsList"` } func (config *Config) GetSensitivePaths() []string { if config == nil || len(config.SensitivePaths) == 0 { return DefaultSensitivePaths } return config.SensitivePaths } 0707010000006F000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/auditors/mounts/fixtures07070100000070000081A400000000000000000000000166C635DC0000014D000000000000000000000000000000000000004200000000kubeaudit-0.22.2/auditors/mounts/fixtures/docker-sock-mounted.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: docker-sock-mounted spec: containers: - name: container image: scratch volumeMounts: - mountPath: /var/run/docker.sock name: docker-sock-volume volumes: - name: docker-sock-volume hostPath: path: /var/run/docker.sock 07070100000071000081A400000000000000000000000166C635DC00000286000000000000000000000000000000000000006100000000kubeaudit-0.22.2/auditors/mounts/fixtures/proc-mounted-allowed-multi-containers-multi-labels.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod container.kubeaudit.io/container1.allow-host-path-mount-proc-volume: "SomeReason" container.kubeaudit.io/container2.allow-host-path-mount-proc-volume: "SomeReason" namespace: proc-mounted-allowed-multi-containers-multi-labels spec: containers: - name: container1 image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume - name: container2 image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume volumes: - name: proc-volume hostPath: path: /proc 07070100000072000081A400000000000000000000000166C635DC00000230000000000000000000000000000000000000006100000000kubeaudit-0.22.2/auditors/mounts/fixtures/proc-mounted-allowed-multi-containers-single-label.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod container.kubeaudit.io/container1.allow-host-path-mount-proc-volume: "SomeReason" namespace: proc-mounted-allowed-multi-containers-single-label spec: containers: - name: container1 image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume - name: container2 image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume volumes: - name: proc-volume hostPath: path: /proc 07070100000073000081A400000000000000000000000166C635DC00000180000000000000000000000000000000000000004300000000kubeaudit-0.22.2/auditors/mounts/fixtures/proc-mounted-allowed.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod kubeaudit.io/allow-host-path-mount-proc-volume: "SomeReason" namespace: proc-mounted-allowed spec: containers: - name: container image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume volumes: - name: proc-volume hostPath: path: /proc 07070100000074000081A400000000000000000000000166C635DC0000011F000000000000000000000000000000000000003B00000000kubeaudit-0.22.2/auditors/mounts/fixtures/proc-mounted.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: proc-mounted spec: containers: - name: container image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume volumes: - name: proc-volume hostPath: path: /proc 07070100000075000081A400000000000000000000000166C635DC00000E95000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/mounts/mounts.gopackage mounts import ( "fmt" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" v1 "k8s.io/api/core/v1" ) const Name = "mounts" const ( // SensitivePathsMounted occurs when a container has sensitive host paths mounted SensitivePathsMounted = "SensitivePathsMounted" ) // DefaultSensitivePaths is the default list of sensitive mount paths (from Falco rule: https://github.com/falcosecurity/falco/blob/master/rules/falco_rules.yaml#L1945) var DefaultSensitivePaths = []string{"/proc", "/var/run/docker.sock", "/", "/etc", "/root", "/var/run/crio/crio.sock", "/run/containerd/containerd.sock", "/home/admin", "/var/lib/kubelet", "/var/lib/kubelet/pki", "/etc/kubernetes", "/etc/kubernetes/manifests"} const overrideLabelPrefix = "allow-host-path-mount-" const ( MountNameMetadataKey = "MountName" MountPathMetadataKey = "MountPath" MountReadOnlyMetadataKey = "MountReadOnly" MountVolumeNameKey = "MountVolume" MountVolumeHostPathKey = "MountVolumeHostPath" ) // SensitivePathMounts implements Auditable type SensitivePathMounts struct { sensitivePaths map[string]bool } func New(config Config) *SensitivePathMounts { paths := make(map[string]bool) for _, path := range config.GetSensitivePaths() { paths[path] = true } return &SensitivePathMounts{ sensitivePaths: paths, } } // Audit checks that the container does not have any sensitive host path func (sensitive *SensitivePathMounts) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult spec := k8s.GetPodSpec(resource) if spec == nil { return auditResults, nil } sensitiveVolumes := auditPodVolumes(spec, sensitive.sensitivePaths) if len(sensitiveVolumes) == 0 { return auditResults, nil } for _, container := range k8s.GetContainers(resource) { for _, auditResult := range auditContainer(container, sensitiveVolumes) { auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, getOverrideLabel(auditResult.Metadata[MountNameMetadataKey])) if auditResult != nil { auditResults = append(auditResults, auditResult) } } } return auditResults, nil } func auditPodVolumes(podSpec *k8s.PodSpecV1, sensitivePaths map[string]bool) map[string]v1.Volume { if podSpec.Volumes == nil { return nil } found := make(map[string]v1.Volume) for _, volume := range podSpec.Volumes { if volume.HostPath == nil { continue } if _, ok := sensitivePaths[volume.HostPath.Path]; ok { found[volume.Name] = volume } } return found } func auditContainer(container *k8s.ContainerV1, sensitiveVolumes map[string]v1.Volume) []*kubeaudit.AuditResult { if container.VolumeMounts == nil { return nil } var auditResults []*kubeaudit.AuditResult for _, mount := range container.VolumeMounts { if volume, ok := sensitiveVolumes[mount.Name]; ok { auditResults = append(auditResults, &kubeaudit.AuditResult{ Auditor: Name, Rule: SensitivePathsMounted, Severity: kubeaudit.Error, Message: fmt.Sprintf("Sensitive path mounted as volume: %s (hostPath: %s). It should be removed from the container's mounts list.", mount.Name, volume.HostPath.Path), Metadata: kubeaudit.Metadata{ "Container": container.Name, MountNameMetadataKey: mount.Name, MountPathMetadataKey: mount.MountPath, MountReadOnlyMetadataKey: fmt.Sprintf("%t", mount.ReadOnly), MountVolumeNameKey: volume.Name, MountVolumeHostPathKey: volume.HostPath.Path, }, }) } } return auditResults } func getOverrideLabel(mountName string) string { return overrideLabelPrefix + mountName } 07070100000076000081A400000000000000000000000166C635DC00000494000000000000000000000000000000000000003000000000kubeaudit-0.22.2/auditors/mounts/mounts_test.gopackage mounts import ( "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestSensitivePathsMounted(t *testing.T) { cases := []struct { file string fixtureDir string expectedErrors []string }{ {"docker-sock-mounted.yml", fixtureDir, []string{SensitivePathsMounted}}, {"proc-mounted.yml", fixtureDir, []string{SensitivePathsMounted}}, {"proc-mounted-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(SensitivePathsMounted)}}, {"proc-mounted-allowed-multi-containers-multi-labels.yml", fixtureDir, []string{override.GetOverriddenResultName(SensitivePathsMounted)}}, {"proc-mounted-allowed-multi-containers-single-label.yml", fixtureDir, []string{SensitivePathsMounted, override.GetOverriddenResultName(SensitivePathsMounted)}}, } config := Config{} for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { test.AuditManifest(t, tc.fixtureDir, tc.file, New(config), tc.expectedErrors) test.AuditLocal(t, tc.fixtureDir, tc.file, New(config), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 07070100000077000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002200000000kubeaudit-0.22.2/auditors/netpols07070100000078000081A400000000000000000000000166C635DC0000062C000000000000000000000000000000000000002900000000kubeaudit-0.22.2/auditors/netpols/fix.gopackage netpols import ( "fmt" "strings" "github.com/Shopify/kubeaudit/pkg/k8s" ) const DefaultDenyNetworkPolicyName = "default-deny" type fixByAddingNetworkPolicy struct { policyList []string namespace string } func (f *fixByAddingNetworkPolicy) Plan() string { return fmt.Sprintf("Create a new NetworkPolicy resource which denies all %s traffic", strings.Join(f.policyList, " and ")) } func (f *fixByAddingNetworkPolicy) Apply(resource k8s.Resource) []k8s.Resource { return []k8s.Resource{newDefaultDenyNetworkPolicy(f.namespace, f.policyList)} } type fixByAddingPolicyToNetPol struct { networkPolicy *k8s.NetworkPolicyV1 policyType string } func (f *fixByAddingPolicyToNetPol) Plan() string { return fmt.Sprintf("Add the '%s' policy type to the network policy", f.policyType) } func (f *fixByAddingPolicyToNetPol) Apply(resource k8s.Resource) []k8s.Resource { f.networkPolicy.Spec.PolicyTypes = append(f.networkPolicy.Spec.PolicyTypes, k8s.PolicyTypeV1(f.policyType)) return nil } func newDefaultDenyNetworkPolicy(namespace string, policyList []string) k8s.Resource { policies := make([]k8s.PolicyTypeV1, 0, len(policyList)) for _, policy := range policyList { policies = append(policies, k8s.PolicyTypeV1(policy)) } networkPolicy := &k8s.NetworkPolicyV1{ ObjectMeta: k8s.ObjectMetaV1{ Name: DefaultDenyNetworkPolicyName, Namespace: namespace, }, Spec: k8s.NetworkPolicySpecV1{ PolicyTypes: policies, }, } networkPolicy.Kind = "NetworkPolicy" networkPolicy.APIVersion = "networking.k8s.io/v1" return networkPolicy } 07070100000079000081A400000000000000000000000166C635DC000004F7000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/auditors/netpols/fix_test.gopackage netpols import ( "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/stretchr/testify/assert" ) func TestFixDefaultDenyNetworkPolicies(t *testing.T) { cases := []struct { file string expectedDenyAllIngress bool expectedDenyAllEgress bool }{ {"namespace-missing-default-deny-netpol.yml", true, true}, {"namespace-missing-default-deny-egress-netpol.yml", true, true}, {"namespace-missing-default-deny-ingress-netpol.yml", true, true}, {"namespace-has-default-deny-netpol.yml", true, true}, {"namespace-has-default-deny-and-allow-all-netpol.yml", true, true}, {"namespace-missing-default-deny-netpol-allowed.yml", false, false}, {"namespace-missing-default-deny-egress-netpol-allowed.yml", true, false}, {"namespace-missing-default-deny-ingress-netpol-allowed.yml", false, true}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { assert := assert.New(t) resources, _ := test.FixSetup(t, fixtureDir, tc.file, New()) networkPolicies := getNetworkPolicies(resources, strings.Split(tc.file, ".")[0]) assert.Equal(tc.expectedDenyAllIngress, hasDenyAllIngress(networkPolicies)) assert.Equal(tc.expectedDenyAllEgress, hasDenyAllEgress(networkPolicies)) }) } } 0707010000007A000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/netpols/fixtures0707010000007B000081A400000000000000000000000166C635DC0000026A000000000000000000000000000000000000006600000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-allow-missing-default-deny-ingress-old-label.yml# this is to test backwards compatibility with old unregistered annotations (kubernetes.io) apiVersion: v1 kind: Namespace metadata: name: namespace-allow-missing-default-deny-ingress-old-label labels: audit.kubernetes.io/namespace.allow-non-default-deny-ingress-network-policy: "SomeReason" --- # https://kubernetes.io/docs/concepts/services-networking/network-policies/#default-deny-all-ingress-traffic apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: namespace-allow-missing-default-deny-ingress-old-label spec: podSelector: {} policyTypes: - Egress 0707010000007C000081A400000000000000000000000166C635DC00000233000000000000000000000000000000000000005F00000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-has-default-deny-and-allow-all-netpol.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-has-default-deny-and-allow-all-netpol --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: namespace-has-default-deny-and-allow-all-netpol spec: podSelector: {} policyTypes: - Ingress - Egress --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-all namespace: namespace-has-default-deny-and-allow-all-netpol spec: podSelector: {} ingress: - {} egress: - {} policyTypes: - Ingress - Egress 0707010000007D000081A400000000000000000000000166C635DC0000011C000000000000000000000000000000000000005100000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-has-default-deny-netpol.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-has-default-deny-netpol --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: namespace-has-default-deny-netpol spec: podSelector: {} policyTypes: - Ingress - Egress 0707010000007E000081A400000000000000000000000166C635DC000001F8000000000000000000000000000000000000006400000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-missing-default-deny-egress-netpol-allowed.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-missing-default-deny-egress-netpol-allowed labels: kubeaudit.io/allow-non-default-deny-egress-network-policy: "SomeReason" --- # https://kubernetes.io/docs/concepts/services-networking/network-policies/#default-deny-all-ingress-traffic apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: namespace-missing-default-deny-egress-netpol-allowed spec: podSelector: {} policyTypes: - Ingress 0707010000007F000081A400000000000000000000000166C635DC00000192000000000000000000000000000000000000005C00000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-missing-default-deny-egress-netpol.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-missing-default-deny-egress-netpol --- # https://kubernetes.io/docs/concepts/services-networking/network-policies/#default-deny-all-ingress-traffic apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: namespace-missing-default-deny-egress-netpol spec: podSelector: {} policyTypes: - Ingress 07070100000080000081A400000000000000000000000166C635DC000001FA000000000000000000000000000000000000006500000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-missing-default-deny-ingress-netpol-allowed.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-missing-default-deny-ingress-netpol-allowed labels: kubeaudit.io/allow-non-default-deny-ingress-network-policy: "SomeReason" --- # https://kubernetes.io/docs/concepts/services-networking/network-policies/#default-deny-all-ingress-traffic apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: namespace-missing-default-deny-ingress-netpol-allowed spec: podSelector: {} policyTypes: - Egress 07070100000081000081A400000000000000000000000166C635DC00000192000000000000000000000000000000000000005D00000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-missing-default-deny-ingress-netpol.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-missing-default-deny-ingress-netpol --- # https://kubernetes.io/docs/concepts/services-networking/network-policies/#default-deny-all-egress-traffic apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: namespace-missing-default-deny-ingress-netpol spec: podSelector: {} policyTypes: - Egress 07070100000082000081A400000000000000000000000166C635DC00000379000000000000000000000000000000000000005D00000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-missing-default-deny-netpol-allowed.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-missing-default-deny-netpol-allowed labels: kubeaudit.io/allow-non-default-deny-egress-network-policy: "SomeReason" kubeaudit.io/allow-non-default-deny-ingress-network-policy: "SomeReason" --- # https://github.com/ahmetb/kubernetes-network-policy-recipes/blob/master/07-allow-traffic-from-some-pods-in-another-namespace.md kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: web-allow-all-ns-monitoring namespace: namespace-missing-default-deny-netpol-allowed spec: podSelector: matchLabels: app: web ingress: - from: - namespaceSelector: # chooses all pods in namespaces labelled with team=operations matchLabels: team: operations podSelector: # chooses pods with type=monitoring matchLabels: type: monitoring 07070100000083000081A400000000000000000000000166C635DC000002C6000000000000000000000000000000000000005500000000kubeaudit-0.22.2/auditors/netpols/fixtures/namespace-missing-default-deny-netpol.ymlapiVersion: v1 kind: Namespace metadata: name: namespace-missing-default-deny-netpol --- # https://github.com/ahmetb/kubernetes-network-policy-recipes/blob/master/07-allow-traffic-from-some-pods-in-another-namespace.md kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: web-allow-all-ns-monitoring namespace: namespace-missing-default-deny-netpol spec: podSelector: matchLabels: app: web ingress: - from: - namespaceSelector: # chooses all pods in namespaces labelled with team=operations matchLabels: team: operations podSelector: # chooses pods with type=monitoring matchLabels: type: monitoring 07070100000084000081A400000000000000000000000166C635DC00001EDA000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/auditors/netpols/netpols.gopackage netpols import ( "fmt" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "netpols" const ( // MissingDefaultDenyIngressAndEgressNetworkPolicy occurs when there is no default deny network policy for // ingress and egress traffic MissingDefaultDenyIngressAndEgressNetworkPolicy = "MissingDefaultDenyIngressAndEgressNetworkPolicy" // MissingDefaultDenyIngressNetworkPolicy occurs when there is no default deny network policy for // ingress traffic MissingDefaultDenyIngressNetworkPolicy = "MissingDefaultDenyIngressNetworkPolicy" // MissingDefaultDenyEgressNetworkPolicy occurs when there is no default deny network policy for // egress traffic MissingDefaultDenyEgressNetworkPolicy = "MissingDefaultDenyEgressNetworkPolicy" // AllowAllIngressNetworkPolicyExists occurs when there is a network policy which allows all ingress traffic AllowAllIngressNetworkPolicyExists = "AllowAllIngressNetworkPolicyExists" // AllowAllEgressNetworkPolicyExists occurs when there is a network policy which allows all egress traffic AllowAllEgressNetworkPolicyExists = "AllowAllEgressNetworkPolicyExists" ) const ( IngressOverrideLabel = "allow-non-default-deny-ingress-network-policy" EgressOverrideLabel = "allow-non-default-deny-egress-network-policy" Ingress = "Ingress" Egress = "Egress" ) // DefaultDenyNetworkPolicies implements Auditable type DefaultDenyNetworkPolicies struct{} func New() *DefaultDenyNetworkPolicies { return &DefaultDenyNetworkPolicies{} } // Audit checks that each namespace resource has a default deny NetworkPolicy for all ingress and egress traffic func (a *DefaultDenyNetworkPolicies) Audit(resource k8s.Resource, resources []k8s.Resource) ([]*kubeaudit.AuditResult, error) { if !k8s.IsNamespaceV1(resource) { return nil, nil } var auditResults []*kubeaudit.AuditResult auditResults = append(auditResults, auditNetworkPoliciesForAllowAll(resource, resources)...) auditResults = append(auditResults, auditNetworkPoliciesForDenyAll(resource, resources)...) return auditResults, nil } func auditNetworkPoliciesForAllowAll(resource k8s.Resource, resources []k8s.Resource) []*kubeaudit.AuditResult { var auditResults []*kubeaudit.AuditResult namespace := getResourceNamespace(resource) networkPolicies := getNetworkPolicies(resources, namespace) for _, networkPolicy := range networkPolicies { auditResults = append(auditResults, auditNetworkPolicy(networkPolicy)...) } return auditResults } func auditNetworkPolicy(networkPolicy *k8s.NetworkPolicyV1) []*kubeaudit.AuditResult { var auditResults []*kubeaudit.AuditResult if allIngressTrafficAllowed(networkPolicy) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: AllowAllIngressNetworkPolicyExists, Severity: kubeaudit.Warn, Message: "Found allow all ingress traffic NetworkPolicy.", Metadata: kubeaudit.Metadata{ "PolicyName": networkPolicy.ObjectMeta.Name, }, } auditResults = append(auditResults, auditResult) } if allEgressTrafficAllowed(networkPolicy) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: AllowAllEgressNetworkPolicyExists, Severity: kubeaudit.Warn, Message: "Found allow all egress traffic NetworkPolicy.", Metadata: kubeaudit.Metadata{ "PolicyName": networkPolicy.ObjectMeta.Name, }, } auditResults = append(auditResults, auditResult) } return auditResults } func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resource) []*kubeaudit.AuditResult { var auditResults []*kubeaudit.AuditResult namespace := getResourceNamespace(resource) networkPolicies := getNetworkPolicies(resources, namespace) hasCatchAllNetPol, catchAllNetPol := hasCatchAllNetworkPolicy(networkPolicies) hasDefaultDenyIngress := hasDenyAllIngress(networkPolicies) hasDefaultDenyEgress := hasDenyAllEgress(networkPolicies) if hasCatchAllNetPol { if !hasDefaultDenyIngress { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: MissingDefaultDenyIngressNetworkPolicy, Severity: kubeaudit.Error, Message: fmt.Sprintf("All ingress traffic should be blocked by default for namespace %s.", namespace), Metadata: kubeaudit.Metadata{ "Namespace": namespace, }, PendingFix: &fixByAddingPolicyToNetPol{ networkPolicy: catchAllNetPol, policyType: Ingress, }, } auditResult = override.ApplyOverride(auditResult, Name, "", resource, IngressOverrideLabel) auditResults = append(auditResults, auditResult) } if !hasDefaultDenyEgress { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: MissingDefaultDenyEgressNetworkPolicy, Severity: kubeaudit.Error, Message: fmt.Sprintf("All egress traffic should be blocked by default for namespace %s.", namespace), Metadata: kubeaudit.Metadata{ "Namespace": namespace, }, PendingFix: &fixByAddingPolicyToNetPol{ networkPolicy: catchAllNetPol, policyType: Egress, }, } auditResult = override.ApplyOverride(auditResult, Name, "", resource, EgressOverrideLabel) auditResults = append(auditResults, auditResult) } return auditResults } // We need to manually figure out the overrides because this case involves two override labels hasIngressOverride, ingressOverrideReason := override.GetResourceOverrideReason(resource, IngressOverrideLabel) hasEgressOverride, egressOverrideReason := override.GetResourceOverrideReason(resource, EgressOverrideLabel) if !hasIngressOverride && !hasEgressOverride { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: MissingDefaultDenyIngressAndEgressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny ingress and egress NetworkPolicy.", Metadata: kubeaudit.Metadata{ "Namespace": namespace, }, PendingFix: &fixByAddingNetworkPolicy{ policyList: []string{"Ingress", "Egress"}, namespace: namespace, }, } return []*kubeaudit.AuditResult{auditResult} } if hasIngressOverride && hasEgressOverride { auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: override.GetOverriddenResultName(MissingDefaultDenyIngressAndEgressNetworkPolicy), Severity: kubeaudit.Warn, Message: "Namespace is missing a default deny ingress and egress NetworkPolicy.", Metadata: kubeaudit.Metadata{ "Namespace": namespace, "OverrideReason": fmt.Sprintf("Ingress: %s, Egress: %s", ingressOverrideReason, egressOverrideReason), }, } return []*kubeaudit.AuditResult{auditResult} } // At this point there is exactly one override label for either ingress or egress which means one needs to be // fixed and the other is overridden auditResult := &kubeaudit.AuditResult{ Auditor: Name, Rule: MissingDefaultDenyIngressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny ingress NetworkPolicy.", Metadata: kubeaudit.Metadata{ "Namespace": namespace, }, PendingFix: &fixByAddingNetworkPolicy{ policyList: []string{Ingress}, namespace: namespace, }, } auditResult = override.ApplyOverride(auditResult, Name, "", resource, IngressOverrideLabel) auditResults = append(auditResults, auditResult) auditResult = &kubeaudit.AuditResult{ Auditor: Name, Rule: MissingDefaultDenyEgressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny egress NetworkPolicy.", Metadata: kubeaudit.Metadata{ "Namespace": namespace, }, PendingFix: &fixByAddingNetworkPolicy{ policyList: []string{Egress}, namespace: namespace, }, } auditResult = override.ApplyOverride(auditResult, Name, "", resource, EgressOverrideLabel) auditResults = append(auditResults, auditResult) return auditResults } 07070100000085000081A400000000000000000000000166C635DC0000072B000000000000000000000000000000000000003200000000kubeaudit-0.22.2/auditors/netpols/netpols_test.gopackage netpols import ( "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditDefaultDenyNetworkPolicies(t *testing.T) { cases := []struct { file string expectedErrors []string }{ {"namespace-missing-default-deny-netpol.yml", []string{MissingDefaultDenyIngressAndEgressNetworkPolicy}}, {"namespace-missing-default-deny-egress-netpol.yml", []string{MissingDefaultDenyEgressNetworkPolicy}}, {"namespace-missing-default-deny-ingress-netpol.yml", []string{MissingDefaultDenyIngressNetworkPolicy}}, {"namespace-has-default-deny-netpol.yml", nil}, {"namespace-has-default-deny-and-allow-all-netpol.yml", []string{AllowAllIngressNetworkPolicyExists, AllowAllEgressNetworkPolicyExists}}, {"namespace-missing-default-deny-netpol-allowed.yml", []string{override.GetOverriddenResultName(MissingDefaultDenyIngressAndEgressNetworkPolicy)}}, {"namespace-missing-default-deny-egress-netpol-allowed.yml", []string{override.GetOverriddenResultName(MissingDefaultDenyEgressNetworkPolicy)}}, {"namespace-missing-default-deny-ingress-netpol-allowed.yml", []string{override.GetOverriddenResultName(MissingDefaultDenyIngressNetworkPolicy)}}, {"namespace-allow-missing-default-deny-ingress-old-label.yml", []string{override.GetOverriddenResultName(MissingDefaultDenyIngressNetworkPolicy)}}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, fixtureDir, tc.file, New(), tc.expectedErrors) test.AuditLocal(t, fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 07070100000086000081A400000000000000000000000166C635DC00000AE4000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/auditors/netpols/util.gopackage netpols import "github.com/Shopify/kubeaudit/pkg/k8s" const AllNamespaces = "" func getNetworkPolicies(resources []k8s.Resource, namespace string) (networkPolicies []*k8s.NetworkPolicyV1) { for _, resource := range resources { networkPolicy, ok := resource.(*k8s.NetworkPolicyV1) if ok && (namespace == AllNamespaces || getResourceNamespace(resource) == namespace) { networkPolicies = append(networkPolicies, networkPolicy) } } return } // isNetworkPolicyType checks if the NetworkPolicy applies to the specified policy type (Ingress or Egress) func isNetworkPolicyType(netPol *k8s.NetworkPolicyV1, netPolType string) bool { for _, polType := range netPol.Spec.PolicyTypes { if string(polType) == netPolType { return true } } return false } func getResourceNamespace(resource k8s.Resource) string { switch kubeType := resource.(type) { case *k8s.NamespaceV1: return kubeType.ObjectMeta.Name case *k8s.NetworkPolicyV1: return kubeType.ObjectMeta.Namespace } return "" } func allIngressTrafficAllowed(networkPolicy *k8s.NetworkPolicyV1) bool { for _, ingress := range networkPolicy.Spec.Ingress { if (len(ingress.From)) == 0 { return true } } return false } func allEgressTrafficAllowed(networkPolicy *k8s.NetworkPolicyV1) bool { for _, egress := range networkPolicy.Spec.Egress { if (len(egress.To)) == 0 { return true } } return false } func hasCatchAllNetworkPolicy(networkPolicies []*k8s.NetworkPolicyV1) (bool, *k8s.NetworkPolicyV1) { for _, networkPolicy := range networkPolicies { if isCatchAllNetworkPolicy(networkPolicy) { return true, networkPolicy } } return false, nil } func isCatchAllNetworkPolicy(networkPolicy *k8s.NetworkPolicyV1) bool { // No PodSelector is set via MatchLabels -> Catch all pods if len(networkPolicy.Spec.PodSelector.MatchLabels) > 0 { return false } // No PodSelector is set via MatchExpressions -> Catch all Pods if len(networkPolicy.Spec.PodSelector.MatchExpressions) > 0 { return false } return true } func hasDenyAllIngress(networkPolicies []*k8s.NetworkPolicyV1) bool { for _, networkPolicy := range networkPolicies { if networkPolicy == nil { continue } if len(networkPolicy.Spec.Ingress) != 0 { continue } if !isNetworkPolicyType(networkPolicy, Ingress) { continue } if isCatchAllNetworkPolicy(networkPolicy) { return true } } return false } func hasDenyAllEgress(networkPolicies []*k8s.NetworkPolicyV1) bool { for _, networkPolicy := range networkPolicies { if networkPolicy == nil { continue } if len(networkPolicy.Spec.Egress) != 0 { continue } if !isNetworkPolicyType(networkPolicy, Egress) { continue } if isCatchAllNetworkPolicy(networkPolicy) { return true } } return false } 07070100000087000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002200000000kubeaudit-0.22.2/auditors/nonroot07070100000088000081A400000000000000000000000166C635DC000002B3000000000000000000000000000000000000002900000000kubeaudit-0.22.2/auditors/nonroot/fix.gopackage nonroot import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" ) type fixRunAsNonRoot struct { container *k8s.ContainerV1 } func (f *fixRunAsNonRoot) Plan() string { return fmt.Sprintf("Set runAsNonRoot to 'true' in container SecurityContext for container %s", f.container.Name) } func (f *fixRunAsNonRoot) Apply(resource k8s.Resource) []k8s.Resource { if f.container.SecurityContext == nil { f.container.SecurityContext = &k8s.SecurityContextV1{} } if f.container.SecurityContext.RunAsUser != nil && *f.container.SecurityContext.RunAsUser == 0 { f.container.SecurityContext.RunAsUser = nil } f.container.SecurityContext.RunAsNonRoot = k8s.NewTrue() return nil } 07070100000089000081A400000000000000000000000166C635DC00000F14000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/auditors/nonroot/fix_test.gopackage nonroot import ( "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" ) func TestFixRunAsNonRoot(t *testing.T) { cases := []struct { file string fixtureDir string expectedValue *bool }{ {"run-as-non-root-nil.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-false.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-false-allowed.yml", fixtureDir, k8s.NewFalse()}, {"run-as-non-root-redundant-override-container.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-redundant-override-pod.yml", fixtureDir, nil}, {"run-as-non-root-psc-false.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-psc-true-csc-false.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-psc-false-csc-false.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-psc-false-allowed.yml", fixtureDir, nil}, {"run-as-non-root-psc-false-csc-true.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-psc-false-csc-nil-multiple-cont.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-psc-false-csc-true-multiple-cont.yml", fixtureDir, k8s.NewTrue()}, {"run-as-non-root-psc-false-allowed-multi-containers-single-label.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-0.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-0-allowed.yml", fixtureDir, nil}, {"run-as-user-psc-0.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-psc-0-allowed.yml", fixtureDir, nil}, {"run-as-user-psc-1.yml", fixtureDir, nil}, {"run-as-user-psc-1-csc-0.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-psc-0-csc-0.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-psc-0-csc-1.yml", fixtureDir, nil}, {"run-as-user-redundant-override-container.yml", fixtureDir, nil}, {"run-as-user-redundant-override-pod.yml", fixtureDir, nil}, {"run-as-user-psc-0-csc-1-multiple-cont.yml", fixtureDir, nil}, {"run-as-user-psc-0-allowed-multi-containers-multi-labels.yml", fixtureDir, nil}, {"run-as-user-psc-0-allowed-multi-containers-single-label.yml", fixtureDir, nil}, {"run-as-user-0-run-as-non-root-true.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-0-run-as-non-root-false.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-psc-0-run-as-non-root-psc-true.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-psc-0-run-as-non-root-psc-false.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-1-run-as-non-root-true.yml", fixtureDir, k8s.NewTrue()}, {"run-as-user-1-run-as-non-root-false.yml", fixtureDir, k8s.NewFalse()}, {"run-as-user-psc-1-run-as-non-root-psc-true.yml", fixtureDir, nil}, {"run-as-user-psc-1-run-as-non-root-psc-false.yml", fixtureDir, nil}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, tc.fixtureDir, tc.file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { if tc.expectedValue == nil { assert.True(t, (container.SecurityContext == nil || container.SecurityContext.RunAsNonRoot == nil)) } else { assert.Equal(t, *tc.expectedValue, *container.SecurityContext.RunAsNonRoot) } } } }) } files := []string{ "run-as-non-root-psc-false-allowed-multi-containers-multi-labels.yml", "run-as-user-psc-0-csc-nil-multiple-cont.yml", } for _, file := range files { t.Run(file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { switch container.Name { case "container1": assert.True(t, (container.SecurityContext == nil || container.SecurityContext.RunAsNonRoot == nil)) case "container2": assert.True(t, *container.SecurityContext.RunAsNonRoot) } } } }) } } 0707010000008A000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/nonroot/fixtures0707010000008B000081A400000000000000000000000166C635DC000001BA000000000000000000000000000000000000004D00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-false-allowed.yml--- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-non-root-false-allowed spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" spec: containers: - name: container image: scratch securityContext: runAsNonRoot: false 0707010000008C000081A400000000000000000000000166C635DC0000016A000000000000000000000000000000000000004500000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-false.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-non-root-false spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: runAsNonRoot: false 0707010000008D000081A400000000000000000000000166C635DC0000012D000000000000000000000000000000000000004300000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-nil.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-non-root-nil spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch 0707010000008E000081A400000000000000000000000166C635DC00000207000000000000000000000000000000000000006F00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false-allowed-multi-containers-multi-labels.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod container.kubeaudit.io/container1.allow-run-as-root: "SuperuserPrivilegesNeeded" container.kubeaudit.io/container2.allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-non-root-psc-false-allowed-multi-containers-multi-labels spec: securityContext: runAsNonRoot: false containers: - name: container1 image: scratch - name: container2 image: scratch securityContext: runAsNonRoot: true 0707010000008F000081A400000000000000000000000166C635DC000001E5000000000000000000000000000000000000006F00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false-allowed-multi-containers-single-label.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod container.kubeaudit.io/container1.allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-non-root-psc-false-allowed-multi-containers-single-label spec: securityContext: runAsNonRoot: false containers: - name: container1 image: scratch securityContext: runAsNonRoot: true - name: container2 image: scratch securityContext: runAsNonRoot: false 07070100000090000081A400000000000000000000000166C635DC00000120000000000000000000000000000000000000005100000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false-allowed.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-non-root-psc-false-allowed spec: securityContext: runAsNonRoot: false containers: - name: container image: scratch 07070100000091000081A400000000000000000000000166C635DC000000FD000000000000000000000000000000000000005300000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false-csc-false.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-non-root-psc-false-csc-false spec: securityContext: runAsNonRoot: false containers: - name: container image: scratch securityContext: runAsNonRoot: false 07070100000092000081A400000000000000000000000166C635DC00000135000000000000000000000000000000000000005F00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false-csc-nil-multiple-cont.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-non-root-psc-false-csc-nil-multiple-cont spec: securityContext: runAsNonRoot: false containers: - name: container1 image: scratch securityContext: runAsNonRoot: true - name: container2 image: scratch 07070100000093000081A400000000000000000000000166C635DC00000168000000000000000000000000000000000000006000000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false-csc-true-multiple-cont.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-non-root-psc-false-csc-true-multiple-cont spec: securityContext: runAsNonRoot: false containers: - name: container1 image: scratch securityContext: runAsNonRoot: true - name: container2 image: scratch securityContext: runAsNonRoot: true 07070100000094000081A400000000000000000000000166C635DC000000FB000000000000000000000000000000000000005200000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false-csc-true.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-non-root-psc-false-csc-true spec: securityContext: runAsNonRoot: false containers: - name: container image: scratch securityContext: runAsNonRoot: true 07070100000095000081A400000000000000000000000166C635DC000000D8000000000000000000000000000000000000004900000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-false.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-non-root-psc-false spec: securityContext: runAsNonRoot: false containers: - name: container image: scratch 07070100000096000081A400000000000000000000000166C635DC000000FB000000000000000000000000000000000000005200000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-true-csc-false.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-non-root-psc-true-csc-false spec: securityContext: runAsNonRoot: true containers: - name: container image: scratch securityContext: runAsNonRoot: false 07070100000097000081A400000000000000000000000166C635DC000000D6000000000000000000000000000000000000004800000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-psc-true.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-non-root-psc-true spec: securityContext: runAsNonRoot: true containers: - name: container image: scratch 07070100000098000081A400000000000000000000000166C635DC000001C4000000000000000000000000000000000000005C00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-redundant-override-container.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-non-root-redundant-override-container spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" spec: containers: - name: container image: scratch securityContext: runAsNonRoot: true 07070100000099000081A400000000000000000000000166C635DC00000124000000000000000000000000000000000000005600000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-non-root-redundant-override-pod.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-non-root-redundant-override-pod spec: securityContext: runAsNonRoot: true containers: - name: container image: scratch 0707010000009A000081A400000000000000000000000166C635DC000001AB000000000000000000000000000000000000004500000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-0-allowed.yml--- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-user-0-allowed spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" spec: containers: - name: container image: scratch securityContext: runAsUser: 0 0707010000009B000081A400000000000000000000000166C635DC00000191000000000000000000000000000000000000005300000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-0-run-as-non-root-false.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-user-0-run-as-non-root-false spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: runAsUser: 0 runAsNonRoot: false 0707010000009C000081A400000000000000000000000166C635DC0000018F000000000000000000000000000000000000005200000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-0-run-as-non-root-true.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-user-0-run-as-non-root-true spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: runAsUser: 0 runAsNonRoot: true 0707010000009D000081A400000000000000000000000166C635DC0000015B000000000000000000000000000000000000003D00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-0.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-user-0 spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: runAsUser: 0 0707010000009E000081A400000000000000000000000166C635DC00000191000000000000000000000000000000000000005300000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-1-run-as-non-root-false.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-user-1-run-as-non-root-false spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: runAsUser: 1 runAsNonRoot: false 0707010000009F000081A400000000000000000000000166C635DC0000018F000000000000000000000000000000000000005200000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-1-run-as-non-root-true.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-user-1-run-as-non-root-true spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch securityContext: runAsUser: 1 runAsNonRoot: true 070701000000A0000081A400000000000000000000000166C635DC000001F2000000000000000000000000000000000000006700000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-allowed-multi-containers-multi-labels.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod container.kubeaudit.io/container1.allow-run-as-root: "SuperuserPrivilegesNeeded" container.kubeaudit.io/container2.allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-user-psc-0-allowed-multi-containers-multi-labels spec: securityContext: runAsUser: 0 containers: - name: container1 image: scratch - name: container2 image: scratch securityContext: runAsUser: 1 070701000000A1000081A400000000000000000000000166C635DC000001C9000000000000000000000000000000000000006700000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-allowed-multi-containers-single-label.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod container.kubeaudit.io/container2.allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-user-psc-0-allowed-multi-containers-single-label spec: securityContext: runAsUser: 0 containers: - name: container1 image: scratch securityContext: runAsUser: 1 - name: container2 image: scratch securityContext: runAsUser: 0 070701000000A2000081A400000000000000000000000166C635DC00000111000000000000000000000000000000000000004900000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-allowed.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-user-psc-0-allowed spec: securityContext: runAsUser: 0 containers: - name: container image: scratch 070701000000A3000081A400000000000000000000000166C635DC000000E3000000000000000000000000000000000000004700000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-csc-0.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-user-psc-0-csc-0 spec: securityContext: runAsUser: 0 containers: - name: container image: scratch securityContext: runAsUser: 0 070701000000A4000081A400000000000000000000000166C635DC0000014A000000000000000000000000000000000000005500000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-csc-1-multiple-cont.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-user-psc-0-csc-1-multiple-cont spec: securityContext: runAsUser: 0 containers: - name: container1 image: scratch securityContext: runAsUser: 1 - name: container2 image: scratch securityContext: runAsUser: 1 070701000000A5000081A400000000000000000000000166C635DC000000E3000000000000000000000000000000000000004700000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-csc-1.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-user-psc-0-csc-1 spec: securityContext: runAsUser: 0 containers: - name: container image: scratch securityContext: runAsUser: 1 070701000000A6000081A400000000000000000000000166C635DC00000120000000000000000000000000000000000000005700000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-csc-nil-multiple-cont.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-user-psc-0-csc-nil-multiple-cont spec: securityContext: runAsUser: 0 containers: - name: container1 image: scratch securityContext: runAsUser: 1 - name: container2 image: scratch 070701000000A7000081A400000000000000000000000166C635DC000000FB000000000000000000000000000000000000005B00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-run-as-non-root-psc-false.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-user-psc-0-run-as-non-root-psc-false spec: securityContext: runAsUser: 0 runAsNonRoot: false containers: - name: container image: scratch 070701000000A8000081A400000000000000000000000166C635DC000000F9000000000000000000000000000000000000005A00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0-run-as-non-root-psc-true.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-user-psc-0-run-as-non-root-psc-true spec: securityContext: runAsUser: 0 runAsNonRoot: true containers: - name: container image: scratch 070701000000A9000081A400000000000000000000000166C635DC000000C9000000000000000000000000000000000000004100000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-0.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-user-psc-0 spec: securityContext: runAsUser: 0 containers: - name: container image: scratch 070701000000AA000081A400000000000000000000000166C635DC000000E3000000000000000000000000000000000000004700000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-1-csc-0.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: run-as-user-psc-1-csc-0 spec: securityContext: runAsUser: 1 containers: - name: container image: scratch securityContext: runAsUser: 0 070701000000AB000081A400000000000000000000000166C635DC000000FB000000000000000000000000000000000000005B00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-1-run-as-non-root-psc-false.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-user-psc-1-run-as-non-root-psc-false spec: securityContext: runAsUser: 1 runAsNonRoot: false containers: - name: container image: scratch 070701000000AC000081A400000000000000000000000166C635DC000000F9000000000000000000000000000000000000005A00000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-1-run-as-non-root-psc-true.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-user-psc-1-run-as-non-root-psc-true spec: securityContext: runAsUser: 1 runAsNonRoot: true containers: - name: container image: scratch 070701000000AD000081A400000000000000000000000166C635DC000000C9000000000000000000000000000000000000004100000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-psc-1.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod namespace: run-as-user-psc-1 spec: securityContext: runAsUser: 1 containers: - name: container image: scratch 070701000000AE000081A400000000000000000000000166C635DC000001BA000000000000000000000000000000000000005800000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-redundant-override-container.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-user-redundant-override-container spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" spec: containers: - name: container image: scratch securityContext: runAsUser: 1 070701000000AF000081A400000000000000000000000166C635DC0000011A000000000000000000000000000000000000005200000000kubeaudit-0.22.2/auditors/nonroot/fixtures/run-as-user-redundant-override-pod.ymlapiVersion: v1 kind: Pod metadata: name: pod labels: name: pod kubeaudit.io/allow-run-as-root: "SuperuserPrivilegesNeeded" namespace: run-as-user-redundant-override-pod spec: securityContext: runAsUser: 1 containers: - name: container image: scratch 070701000000B0000081A400000000000000000000000166C635DC00001830000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/auditors/nonroot/nonroot.gopackage nonroot import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "nonroot" const ( // RunAsUserCSCRoot occurs when runAsUser is set to 0 in the container SecurityContext RunAsUserCSCRoot = "RunAsUserCSCRoot" // RunAsUserPSCRoot occurs when runAsUser is set to 0 in the pod SecurityContext RunAsUserPSCRoot = "RunAsUserPSCRoot" // RunAsNonRootCSCFalse occurs when runAsNonRoot is set to false in the container SecurityContext RunAsNonRootCSCFalse = "RunAsNonRootCSCFalse" // RunAsNonRootPSCNilCSCNil occurs when runAsNonRoot is not set in the container SecurityContext nor the pod // security context. runAsNonRoot defaults to false so this is bad RunAsNonRootPSCNilCSCNil = "RunAsNonRootPSCNilCSCNil" // RunAsNonRootPSCFalseCSCNil occurs when runAsNonRoot is not set in the container SecurityContext and is set to // false in the PodSecurityContext RunAsNonRootPSCFalseCSCNil = "RunAsNonRootPSCFalseCSCNil" ) const OverrideLabel = "allow-run-as-root" // RunAsNonRoot implements Auditable type RunAsNonRoot struct{} func New() *RunAsNonRoot { return &RunAsNonRoot{} } // Audit checks that runAsNonRoot is set to true in every container's security context func (a *RunAsNonRoot) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, resource) auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } } return auditResults, nil } func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudit.AuditResult { podSpec := k8s.GetPodSpec(resource) if podSpec == nil { return nil } if !isContainerRunAsUserNil(container) { if *container.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ Auditor: Name, Rule: RunAsUserCSCRoot, Severity: kubeaudit.Error, Message: "runAsUser is set to UID 0 (root user) in the container SecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", PendingFix: &fixRunAsNonRoot{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if !isPodRunAsUserNil(podSpec) { if *podSpec.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ Auditor: Name, Rule: RunAsUserPSCRoot, Severity: kubeaudit.Warn, Message: "runAsUser is set to UID 0 (root user) in the PodSecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } } return nil } if !isPodRunAsUserNil(podSpec) { if *podSpec.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ Auditor: Name, Rule: RunAsUserPSCRoot, Severity: kubeaudit.Error, Message: "runAsUser is set to UID 0 (root user) in the PodSecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", PendingFix: &fixRunAsNonRoot{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } return nil } if isContainerRunAsNonRootCSCFalse(container) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: RunAsNonRootCSCFalse, Severity: kubeaudit.Error, Message: "runAsNonRoot is set to false in the container SecurityContext. Either set it to true or set runAsUser to a value > 0.", PendingFix: &fixRunAsNonRoot{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if isContainerRunAsNonRootNil(container) { if isPodRunAsNonRootNil(podSpec) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: RunAsNonRootPSCNilCSCNil, Severity: kubeaudit.Error, Message: "runAsNonRoot should be set to true or runAsUser should be set to a value > 0 either in the container SecurityContext or PodSecurityContext.", PendingFix: &fixRunAsNonRoot{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if isPodRunAsNonRootFalse(podSpec) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: RunAsNonRootPSCFalseCSCNil, Severity: kubeaudit.Error, Message: "runAsNonRoot is set to false in the PodSecurityContext. Either set it to true or set runAsUser to a value > 0.", PendingFix: &fixRunAsNonRoot{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } } return nil } // returns true if runAsNonRoot is explicitly set to false in the pod's security context. Returns true if the // security context is nil even though the default value for runAsNonRoot is false func isPodRunAsNonRootFalse(podSpec *k8s.PodSpecV1) bool { if isPodRunAsNonRootNil(podSpec) { return false } return !*podSpec.SecurityContext.RunAsNonRoot } func isPodRunAsNonRootNil(podSpec *k8s.PodSpecV1) bool { return podSpec.SecurityContext == nil || podSpec.SecurityContext.RunAsNonRoot == nil } // returns true if runAsNonRoot is explicitly set to false in the container's security context. Returns true if the // security context is nil even though the default value for runAsNonRoot is false func isContainerRunAsNonRootCSCFalse(container *k8s.ContainerV1) bool { if isContainerRunAsNonRootNil(container) { return false } return !*container.SecurityContext.RunAsNonRoot } func isContainerRunAsNonRootNil(container *k8s.ContainerV1) bool { return container.SecurityContext == nil || container.SecurityContext.RunAsNonRoot == nil } func isContainerRunAsUserNil(container *k8s.ContainerV1) bool { return container.SecurityContext == nil || container.SecurityContext.RunAsUser == nil } func isPodRunAsUserNil(podSpec *k8s.PodSpecV1) bool { return podSpec.SecurityContext == nil || podSpec.SecurityContext.RunAsUser == nil } 070701000000B1000081A400000000000000000000000166C635DC0000110B000000000000000000000000000000000000003200000000kubeaudit-0.22.2/auditors/nonroot/nonroot_test.gopackage nonroot import ( "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditRunAsNonRoot(t *testing.T) { cases := []struct { file string fixtureDir string expectedErrors []string }{ {"run-as-non-root-nil.yml", fixtureDir, []string{RunAsNonRootPSCNilCSCNil}}, {"run-as-non-root-false.yml", fixtureDir, []string{RunAsNonRootCSCFalse}}, {"run-as-non-root-false-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(RunAsNonRootCSCFalse)}}, {"run-as-non-root-redundant-override-container.yml", fixtureDir, []string{kubeaudit.RedundantAuditorOverride}}, {"run-as-non-root-redundant-override-pod.yml", fixtureDir, []string{kubeaudit.RedundantAuditorOverride}}, {"run-as-non-root-psc-false.yml", fixtureDir, []string{RunAsNonRootPSCFalseCSCNil}}, {"run-as-non-root-psc-true.yml", fixtureDir, []string{}}, {"run-as-non-root-psc-true-csc-false.yml", fixtureDir, []string{RunAsNonRootCSCFalse}}, {"run-as-non-root-psc-false-csc-false.yml", fixtureDir, []string{RunAsNonRootCSCFalse}}, {"run-as-non-root-psc-false-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(RunAsNonRootPSCFalseCSCNil)}}, {"run-as-non-root-psc-false-csc-true.yml", fixtureDir, []string{}}, {"run-as-non-root-psc-false-csc-nil-multiple-cont.yml", fixtureDir, []string{RunAsNonRootPSCFalseCSCNil}}, {"run-as-non-root-psc-false-csc-true-multiple-cont.yml", fixtureDir, []string{}}, {"run-as-non-root-psc-false-allowed-multi-containers-multi-labels.yml", fixtureDir, []string{ override.GetOverriddenResultName(RunAsNonRootPSCFalseCSCNil), kubeaudit.RedundantAuditorOverride, }}, {"run-as-non-root-psc-false-allowed-multi-containers-single-label.yml", fixtureDir, []string{ kubeaudit.RedundantAuditorOverride, RunAsNonRootCSCFalse, }}, {"run-as-user-0.yml", fixtureDir, []string{RunAsUserCSCRoot}}, {"run-as-user-0-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(RunAsUserCSCRoot)}}, {"run-as-user-psc-0.yml", fixtureDir, []string{RunAsUserPSCRoot}}, {"run-as-user-psc-0-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(RunAsUserPSCRoot)}}, {"run-as-user-psc-1.yml", fixtureDir, []string{}}, {"run-as-user-psc-1-csc-0.yml", fixtureDir, []string{RunAsUserCSCRoot}}, {"run-as-user-psc-0-csc-0.yml", fixtureDir, []string{RunAsUserCSCRoot}}, {"run-as-user-psc-0-csc-1.yml", fixtureDir, []string{RunAsUserPSCRoot}}, {"run-as-user-redundant-override-container.yml", fixtureDir, []string{kubeaudit.RedundantAuditorOverride}}, {"run-as-user-redundant-override-pod.yml", fixtureDir, []string{kubeaudit.RedundantAuditorOverride}}, {"run-as-user-psc-0-csc-nil-multiple-cont.yml", fixtureDir, []string{RunAsUserPSCRoot}}, {"run-as-user-psc-0-csc-1-multiple-cont.yml", fixtureDir, []string{RunAsUserPSCRoot}}, {"run-as-user-psc-0-allowed-multi-containers-multi-labels.yml", fixtureDir, []string{ override.GetOverriddenResultName(RunAsUserPSCRoot), }}, {"run-as-user-psc-0-allowed-multi-containers-single-label.yml", fixtureDir, []string{ override.GetOverriddenResultName(RunAsUserCSCRoot), RunAsUserPSCRoot, }}, {"run-as-user-0-run-as-non-root-true.yml", fixtureDir, []string{RunAsUserCSCRoot}}, {"run-as-user-0-run-as-non-root-false.yml", fixtureDir, []string{RunAsUserCSCRoot}}, {"run-as-user-psc-0-run-as-non-root-psc-true.yml", fixtureDir, []string{RunAsUserPSCRoot}}, {"run-as-user-psc-0-run-as-non-root-psc-false.yml", fixtureDir, []string{RunAsUserPSCRoot}}, {"run-as-user-1-run-as-non-root-true.yml", fixtureDir, []string{}}, {"run-as-user-1-run-as-non-root-false.yml", fixtureDir, []string{}}, {"run-as-user-psc-1-run-as-non-root-psc-true.yml", fixtureDir, []string{}}, {"run-as-user-psc-1-run-as-non-root-psc-false.yml", fixtureDir, []string{}}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, tc.fixtureDir, tc.file, New(), tc.expectedErrors) test.AuditLocal(t, tc.fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 070701000000B2000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002200000000kubeaudit-0.22.2/auditors/privesc070701000000B3000081A400000000000000000000000166C635DC0000028A000000000000000000000000000000000000002900000000kubeaudit-0.22.2/auditors/privesc/fix.gopackage privesc import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" ) type fixBySettingAllowPrivilegeEscalationFalse struct { container *k8s.ContainerV1 } func (f *fixBySettingAllowPrivilegeEscalationFalse) Plan() string { return fmt.Sprintf("Set AllowPrivilegeEscalation to 'false' in the container SecurityContext for container %s", f.container.Name) } func (f *fixBySettingAllowPrivilegeEscalationFalse) Apply(resource k8s.Resource) []k8s.Resource { if f.container.SecurityContext == nil { f.container.SecurityContext = &k8s.SecurityContextV1{} } f.container.SecurityContext.AllowPrivilegeEscalation = k8s.NewFalse() return nil } 070701000000B4000081A400000000000000000000000166C635DC0000067F000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/auditors/privesc/fix_test.gopackage privesc import ( "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" ) func TestFixPrivilegeEscalation(t *testing.T) { cases := []struct { file string fixtureDir string expectedValue bool }{ {"allow-privilege-escalation-nil.yml", fixtureDir, false}, {"allow-privilege-escalation-redundant-override.yml", fixtureDir, false}, {"allow-privilege-escalation-true-allowed.yml", fixtureDir, true}, {"allow-privilege-escalation-true-multi-allowed-multi-containers.yml", fixtureDir, true}, {"allow-privilege-escalation-true.yml", fixtureDir, false}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, tc.fixtureDir, tc.file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { assert.Equal(t, tc.expectedValue, *container.SecurityContext.AllowPrivilegeEscalation) } } }) } file := "allow-privilege-escalation-true-single-allowed-multi-containers.yml" t.Run(file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { switch container.Name { case "container1": assert.False(t, *container.SecurityContext.AllowPrivilegeEscalation) case "container2": assert.True(t, *container.SecurityContext.AllowPrivilegeEscalation) default: assert.Failf(t, "unexpected container name", container.Name) } } } }) } 070701000000B5000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/privesc/fixtures070701000000B6000081A400000000000000000000000166C635DC00000157000000000000000000000000000000000000004E00000000kubeaudit-0.22.2/auditors/privesc/fixtures/allow-privilege-escalation-nil.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: allow-privilege-escalation-nil spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset spec: containers: - name: container image: scratch 070701000000B7000081A400000000000000000000000166C635DC000001FA000000000000000000000000000000000000005D00000000kubeaudit-0.22.2/auditors/privesc/fixtures/allow-privilege-escalation-redundant-override.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: allow-privilege-escalation-redundant-override spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset kubeaudit.io/allow-privilege-escalation: "SuperuserPrivilegesNeeded" spec: containers: - name: container image: scratch securityContext: allowPrivilegeEscalation: false 070701000000B8000081A400000000000000000000000166C635DC000001DA000000000000000000000000000000000000005700000000kubeaudit-0.22.2/auditors/privesc/fixtures/allow-privilege-escalation-true-allowed.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: allow-privilege-escalation-true-allowed spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset kubeaudit.io/allow-privilege-escalation: "" spec: containers: - name: container image: scratch securityContext: allowPrivilegeEscalation: true 070701000000B9000081A400000000000000000000000166C635DC000002FC000000000000000000000000000000000000006E00000000kubeaudit-0.22.2/auditors/privesc/fixtures/allow-privilege-escalation-true-multi-allowed-multi-containers.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: allow-privilege-escalation-true-multi-allowed-multi-containers spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset container.kubeaudit.io/container1.allow-privilege-escalation: "SuperuserPrivilegesNeeded" container.kubeaudit.io/container2.allow-privilege-escalation: "SuperuserPrivilegesNeeded" spec: containers: - name: container1 image: scratch securityContext: allowPrivilegeEscalation: true - name: container2 image: scratch securityContext: allowPrivilegeEscalation: true 070701000000BA000081A400000000000000000000000166C635DC00000269000000000000000000000000000000000000006F00000000kubeaudit-0.22.2/auditors/privesc/fixtures/allow-privilege-escalation-true-single-allowed-multi-containers.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: allow-privilege-escalation-true-single-allowed-multi-containers spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset container.kubeaudit.io/container2.allow-privilege-escalation: "SuperuserPrivilegesNeeded" spec: containers: - name: container1 securityContext: allowPrivilegeEscalation: true - name: container2 securityContext: allowPrivilegeEscalation: true 070701000000BB000081A400000000000000000000000166C635DC0000019E000000000000000000000000000000000000004F00000000kubeaudit-0.22.2/auditors/privesc/fixtures/allow-privilege-escalation-true.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: allow-privilege-escalation-true spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset spec: containers: - name: container image: scratch securityContext: allowPrivilegeEscalation: true 070701000000BC000081A400000000000000000000000166C635DC00000A83000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/auditors/privesc/privesc.gopackage privesc import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "privesc" const ( // AllowPrivilegeEscalationNil occurs when the AllowPrivilegeEscalation field is missing or unset in the // container SecurityContext AllowPrivilegeEscalationNil = "AllowPrivilegeEscalationNil" // AllowPrivilegeEscalationTrue occurs when the AllowPrivilegeEscalation field is set to true in the container // security context AllowPrivilegeEscalationTrue = "AllowPrivilegeEscalationTrue" ) const OverrideLabel = "allow-privilege-escalation" // AllowPrivilegeEscalation implements Auditable type AllowPrivilegeEscalation struct{} func New() *AllowPrivilegeEscalation { return &AllowPrivilegeEscalation{} } // Audit checks that AllowPrivilegeEscalation is disabled (set to false) in the container SecurityContext func (a *AllowPrivilegeEscalation) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container) auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } } return auditResults, nil } func auditContainer(container *k8s.ContainerV1) *kubeaudit.AuditResult { if isAllowPrivilegeEscalationNil(container) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: AllowPrivilegeEscalationNil, Severity: kubeaudit.Error, Message: "allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'.", PendingFix: &fixBySettingAllowPrivilegeEscalationFalse{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if isAllowPrivilegeEscalationTrue(container) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: AllowPrivilegeEscalationTrue, Severity: kubeaudit.Error, Message: "allowPrivilegeEscalation set to 'true'. It should be set to 'false'.", PendingFix: &fixBySettingAllowPrivilegeEscalationFalse{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } return nil } func isAllowPrivilegeEscalationNil(container *k8s.ContainerV1) bool { return container.SecurityContext == nil || container.SecurityContext.AllowPrivilegeEscalation == nil } func isAllowPrivilegeEscalationTrue(container *k8s.ContainerV1) bool { return container.SecurityContext != nil && *container.SecurityContext.AllowPrivilegeEscalation } 070701000000BD000081A400000000000000000000000166C635DC00000627000000000000000000000000000000000000003200000000kubeaudit-0.22.2/auditors/privesc/privesc_test.gopackage privesc import ( "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditPrivilegeEscalation(t *testing.T) { cases := []struct { file string fixtureDir string expectedErrors []string }{ {"allow-privilege-escalation-nil.yml", fixtureDir, []string{AllowPrivilegeEscalationNil}}, {"allow-privilege-escalation-redundant-override.yml", fixtureDir, []string{kubeaudit.RedundantAuditorOverride}}, {"allow-privilege-escalation-true-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(AllowPrivilegeEscalationTrue)}}, {"allow-privilege-escalation-true-multi-allowed-multi-containers.yml", fixtureDir, []string{override.GetOverriddenResultName(AllowPrivilegeEscalationTrue)}}, {"allow-privilege-escalation-true-single-allowed-multi-containers.yml", fixtureDir, []string{AllowPrivilegeEscalationTrue, override.GetOverriddenResultName(AllowPrivilegeEscalationTrue)}}, {"allow-privilege-escalation-true.yml", fixtureDir, []string{AllowPrivilegeEscalationTrue}}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, tc.fixtureDir, tc.file, New(), tc.expectedErrors) test.AuditLocal(t, tc.fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 070701000000BE000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002500000000kubeaudit-0.22.2/auditors/privileged070701000000BF000081A400000000000000000000000166C635DC00000219000000000000000000000000000000000000002C00000000kubeaudit-0.22.2/auditors/privileged/fix.gopackage privileged import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" ) type fixPrivileged struct { container *k8s.ContainerV1 } func (f *fixPrivileged) Plan() string { return fmt.Sprintf("Set privileged to 'false' in container SecurityContext for container %s", f.container.Name) } func (f *fixPrivileged) Apply(resource k8s.Resource) []k8s.Resource { if f.container.SecurityContext == nil { f.container.SecurityContext = &k8s.SecurityContextV1{} } f.container.SecurityContext.Privileged = k8s.NewFalse() return nil } 070701000000C0000081A400000000000000000000000166C635DC000005FC000000000000000000000000000000000000003100000000kubeaudit-0.22.2/auditors/privileged/fix_test.gopackage privileged import ( "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" ) func TestFixPrivileged(t *testing.T) { cases := []struct { file string fixtureDir string expectedValue bool }{ {"privileged-nil.yml", fixtureDir, false}, {"privileged-true.yml", fixtureDir, false}, {"privileged-true-allowed.yml", fixtureDir, true}, {"privileged-redundant-override.yml", fixtureDir, false}, {"privileged-true-allowed-multi-containers-multi-labels.yml", fixtureDir, true}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, tc.fixtureDir, tc.file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { assert.Equal(t, tc.expectedValue, *container.SecurityContext.Privileged) } } }) } file := "privileged-true-allowed-multi-containers-single-label.yml" t.Run(file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { switch container.Name { case "container1": assert.False(t, *container.SecurityContext.Privileged) case "container2": assert.True(t, *container.SecurityContext.Privileged) default: assert.Failf(t, "unexpected container name", container.Name) } } } }) } 070701000000C1000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/auditors/privileged/fixtures070701000000C2000081A400000000000000000000000166C635DC00000124000000000000000000000000000000000000004100000000kubeaudit-0.22.2/auditors/privileged/fixtures/privileged-nil.ymlapiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset namespace: privileged-nil spec: selector: matchLabels: name: daemonset template: metadata: labels: name: daemonset spec: containers: - name: container image: scratch 070701000000C3000081A400000000000000000000000166C635DC000001A0000000000000000000000000000000000000005000000000kubeaudit-0.22.2/auditors/privileged/fixtures/privileged-redundant-override.ymlapiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset namespace: privileged-redundant-override spec: selector: matchLabels: name: daemonset template: metadata: labels: name: daemonset kubeaudit.io/allow-privileged: "SomeReason" spec: containers: - name: container image: scratch securityContext: privileged: false 070701000000C4000081A400000000000000000000000166C635DC00000286000000000000000000000000000000000000006800000000kubeaudit-0.22.2/auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml--- apiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset namespace: privileged-true-allowed-multi-containers-multi-labels spec: selector: matchLabels: name: daemonset template: metadata: labels: name: daemonset container.kubeaudit.io/container1.allow-privileged: "SomeReason" container.kubeaudit.io/container2.allow-privileged: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: privileged: true - name: container2 image: scratch securityContext: privileged: true 070701000000C5000081A400000000000000000000000166C635DC0000023D000000000000000000000000000000000000006800000000kubeaudit-0.22.2/auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml--- apiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset namespace: privileged-true-allowed-multi-containers-single-label spec: selector: matchLabels: name: daemonset template: metadata: labels: name: daemonset container.kubeaudit.io/container2.allow-privileged: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: privileged: true - name: container2 image: scratch securityContext: privileged: true 070701000000C6000081A400000000000000000000000166C635DC0000019D000000000000000000000000000000000000004A00000000kubeaudit-0.22.2/auditors/privileged/fixtures/privileged-true-allowed.yml--- apiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset namespace: privileged-true-allowed spec: selector: matchLabels: name: daemonset template: metadata: labels: name: daemonset kubeaudit.io/allow-privileged: "SomeReason" spec: containers: - name: container image: scratch securityContext: privileged: true 070701000000C7000081A400000000000000000000000166C635DC0000015D000000000000000000000000000000000000004200000000kubeaudit-0.22.2/auditors/privileged/fixtures/privileged-true.ymlapiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset namespace: privileged-true spec: selector: matchLabels: name: daemonset template: metadata: labels: name: daemonset spec: containers: - name: container image: scratch securityContext: privileged: true 070701000000C8000081A400000000000000000000000166C635DC00000977000000000000000000000000000000000000003300000000kubeaudit-0.22.2/auditors/privileged/privileged.gopackage privileged import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "privileged" const ( // PrivilegedTrue occurs when privileged is set to true in the container SecurityContext PrivilegedTrue = "PrivilegedTrue" // PrivilegedNil occurs when privileged is not set in the container SecurityContext. // Privileged defaults to false so this is ok PrivilegedNil = "PrivilegedNil" ) const OverrideLabel = "allow-privileged" // Privileged implements Auditable type Privileged struct{} func New() *Privileged { return &Privileged{} } // Audit checks that privileged is set to false in every container's security context func (a *Privileged) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, resource) auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } } return auditResults, nil } func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudit.AuditResult { if isPrivilegedNil(container) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: PrivilegedNil, Severity: kubeaudit.Warn, Message: "privileged is not set in container SecurityContext. Privileged defaults to 'false' but it should be explicitly set to 'false'.", PendingFix: &fixPrivileged{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if isPrivilegedTrue(container) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: PrivilegedTrue, Severity: kubeaudit.Error, Message: "privileged is set to 'true' in container SecurityContext. It should be set to 'false'.", PendingFix: &fixPrivileged{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } return nil } func isPrivilegedTrue(container *k8s.ContainerV1) bool { if isPrivilegedNil(container) { return false } return *container.SecurityContext.Privileged } func isPrivilegedNil(container *k8s.ContainerV1) bool { return container.SecurityContext == nil || container.SecurityContext.Privileged == nil } 070701000000C9000081A400000000000000000000000166C635DC00000585000000000000000000000000000000000000003800000000kubeaudit-0.22.2/auditors/privileged/privileged_test.gopackage privileged import ( "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditPrivileged(t *testing.T) { cases := []struct { file string fixtureDir string expectedErrors []string }{ {"privileged-nil.yml", fixtureDir, []string{PrivilegedNil}}, {"privileged-true.yml", fixtureDir, []string{PrivilegedTrue}}, {"privileged-true-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(PrivilegedTrue)}}, {"privileged-redundant-override.yml", fixtureDir, []string{kubeaudit.RedundantAuditorOverride}}, {"privileged-true-allowed-multi-containers-multi-labels.yml", fixtureDir, []string{override.GetOverriddenResultName(PrivilegedTrue)}}, {"privileged-true-allowed-multi-containers-single-label.yml", fixtureDir, []string{ PrivilegedTrue, override.GetOverriddenResultName(PrivilegedTrue)}, }, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, tc.fixtureDir, tc.file, New(), tc.expectedErrors) test.AuditLocal(t, tc.fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 070701000000CA000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002100000000kubeaudit-0.22.2/auditors/rootfs070701000000CB000081A400000000000000000000000166C635DC0000024F000000000000000000000000000000000000002800000000kubeaudit-0.22.2/auditors/rootfs/fix.gopackage rootfs import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" ) type fixReadOnlyRootFilesystem struct { container *k8s.ContainerV1 } func (f *fixReadOnlyRootFilesystem) Plan() string { return fmt.Sprintf("Set readOnlyRootFilesystem to 'true' in container SecurityContext for container %s", f.container.Name) } func (f *fixReadOnlyRootFilesystem) Apply(resource k8s.Resource) []k8s.Resource { if f.container.SecurityContext == nil { f.container.SecurityContext = &k8s.SecurityContextV1{} } f.container.SecurityContext.ReadOnlyRootFilesystem = k8s.NewTrue() return nil } 070701000000CC000081A400000000000000000000000166C635DC00000663000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/auditors/rootfs/fix_test.gopackage rootfs import ( "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" ) func TestFixReadOnlyRootFilesystem(t *testing.T) { cases := []struct { file string fixtureDir string expectedValue bool }{ {"read-only-root-filesystem-nil.yml", fixtureDir, true}, {"read-only-root-filesystem-false.yml", fixtureDir, true}, {"read-only-root-filesystem-false-allowed.yml", fixtureDir, false}, {"read-only-root-filesystem-redundant-override.yml", fixtureDir, true}, {"read-only-root-filesystem-false-allowed-multi-labels.yml", fixtureDir, false}, } for _, tc := range cases { t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, tc.fixtureDir, tc.file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { assert.Equal(t, tc.expectedValue, *container.SecurityContext.ReadOnlyRootFilesystem) } } }) } file := "read-only-root-filesystem-false-allowed-single-label.yml" t.Run(file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, file, New()) for _, resource := range resources { containers := k8s.GetContainers(resource) for _, container := range containers { switch container.Name { case "container1": assert.False(t, *container.SecurityContext.ReadOnlyRootFilesystem) case "container2": assert.True(t, *container.SecurityContext.ReadOnlyRootFilesystem) default: assert.Failf(t, "unexpected container name", container.Name) } } } }) } 070701000000CD000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/auditors/rootfs/fixtures070701000000CE000081A400000000000000000000000166C635DC000002EC000000000000000000000000000000000000006300000000kubeaudit-0.22.2/auditors/rootfs/fixtures/read-only-root-filesystem-false-allowed-multi-labels.yml--- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: read-only-root-filesystem-false-allowed-multi-labels spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset container.kubeaudit.io/container1.allow-read-only-root-filesystem-false: "SomeReason" container.kubeaudit.io/container2.allow-read-only-root-filesystem-false: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: readOnlyRootFilesystem: false - name: container2 image: scratch securityContext: readOnlyRootFilesystem: false 070701000000CF000081A400000000000000000000000166C635DC0000028E000000000000000000000000000000000000006300000000kubeaudit-0.22.2/auditors/rootfs/fixtures/read-only-root-filesystem-false-allowed-single-label.yml--- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: read-only-root-filesystem-false-allowed-single-label spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset container.kubeaudit.io/container1.allow-read-only-root-filesystem-false: "SomeReason" spec: containers: - name: container1 image: scratch securityContext: readOnlyRootFilesystem: false - name: container2 image: scratch securityContext: readOnlyRootFilesystem: false 070701000000D0000081A400000000000000000000000166C635DC000001F2000000000000000000000000000000000000005600000000kubeaudit-0.22.2/auditors/rootfs/fixtures/read-only-root-filesystem-false-allowed.yml--- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: read-only-root-filesystem-false-allowed spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset kubeaudit.io/allow-read-only-root-filesystem-false: "SomeReason" spec: containers: - name: container image: scratch securityContext: readOnlyRootFilesystem: false 070701000000D1000081A400000000000000000000000166C635DC0000019D000000000000000000000000000000000000004E00000000kubeaudit-0.22.2/auditors/rootfs/fixtures/read-only-root-filesystem-false.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: read-only-root-filesystem-false spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset spec: containers: - name: container image: scratch securityContext: readOnlyRootFilesystem: false 070701000000D2000081A400000000000000000000000166C635DC00000156000000000000000000000000000000000000004C00000000kubeaudit-0.22.2/auditors/rootfs/fixtures/read-only-root-filesystem-nil.ymlapiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: read-only-root-filesystem-nil spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset spec: containers: - name: container image: scratch 070701000000D3000081A400000000000000000000000166C635DC000001F6000000000000000000000000000000000000005B00000000kubeaudit-0.22.2/auditors/rootfs/fixtures/read-only-root-filesystem-redundant-override.yml--- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: read-only-root-filesystem-redundant-override spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset kubeaudit.io/allow-read-only-root-filesystem-false: "SomeReason" spec: containers: - name: container image: scratch securityContext: readOnlyRootFilesystem: true 070701000000D4000081A400000000000000000000000166C635DC00000AAD000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/rootfs/rootfs.gopackage rootfs import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/Shopify/kubeaudit/pkg/override" ) const Name = "rootfs" const ( // ReadOnlyRootFilesystemFalse occurs when readOnlyRootFilesystem is set to false in the container SecurityContext ReadOnlyRootFilesystemFalse = "ReadOnlyRootFilesystemFalse" // ReadOnlyRootFilesystemNil occurs when readOnlyRootFilesystem is not set in the container SecurityContext. // readOnlyRootFilesystem defaults to false so this is bad ReadOnlyRootFilesystemNil = "ReadOnlyRootFilesystemNil" ) const OverrideLabel = "allow-read-only-root-filesystem-false" // ReadOnlyRootFilesystem implements Auditable type ReadOnlyRootFilesystem struct{} func New() *ReadOnlyRootFilesystem { return &ReadOnlyRootFilesystem{} } // Audit checks that readOnlyRootFilesystem is set to true in every container's security context func (a *ReadOnlyRootFilesystem) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, resource) auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } } return auditResults, nil } func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudit.AuditResult { if isReadOnlyRootFilesystemNil(container) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: ReadOnlyRootFilesystemNil, Severity: kubeaudit.Error, Message: "readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'.", PendingFix: &fixReadOnlyRootFilesystem{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } if isReadOnlyRootFilesystemFalse(container) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: ReadOnlyRootFilesystemFalse, Severity: kubeaudit.Error, Message: "readOnlyRootFilesystem is set to 'false' in container SecurityContext. It should be set to 'true'.", PendingFix: &fixReadOnlyRootFilesystem{ container: container, }, Metadata: kubeaudit.Metadata{ "Container": container.Name, }, } } return nil } func isReadOnlyRootFilesystemFalse(container *k8s.ContainerV1) bool { if isReadOnlyRootFilesystemNil(container) { return true } return !*container.SecurityContext.ReadOnlyRootFilesystem } func isReadOnlyRootFilesystemNil(container *k8s.ContainerV1) bool { return container.SecurityContext == nil || container.SecurityContext.ReadOnlyRootFilesystem == nil } 070701000000D5000081A400000000000000000000000166C635DC00000613000000000000000000000000000000000000003000000000kubeaudit-0.22.2/auditors/rootfs/rootfs_test.gopackage rootfs import ( "strings" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/override" ) const fixtureDir = "fixtures" func TestAuditReadOnlyRootFilesystem(t *testing.T) { cases := []struct { file string fixtureDir string expectedErrors []string }{ {"read-only-root-filesystem-nil.yml", fixtureDir, []string{ReadOnlyRootFilesystemNil}}, {"read-only-root-filesystem-false.yml", fixtureDir, []string{ReadOnlyRootFilesystemFalse}}, {"read-only-root-filesystem-false-allowed.yml", fixtureDir, []string{override.GetOverriddenResultName(ReadOnlyRootFilesystemFalse)}}, {"read-only-root-filesystem-redundant-override.yml", fixtureDir, []string{kubeaudit.RedundantAuditorOverride}}, {"read-only-root-filesystem-false-allowed-multi-labels.yml", fixtureDir, []string{override.GetOverriddenResultName(ReadOnlyRootFilesystemFalse)}}, {"read-only-root-filesystem-false-allowed-single-label.yml", fixtureDir, []string{ override.GetOverriddenResultName(ReadOnlyRootFilesystemFalse), ReadOnlyRootFilesystemFalse, }}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, tc.fixtureDir, tc.file, New(), tc.expectedErrors) test.AuditLocal(t, tc.fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) }) } } 070701000000D6000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002200000000kubeaudit-0.22.2/auditors/seccomp070701000000D7000081A400000000000000000000000166C635DC00000745000000000000000000000000000000000000002900000000kubeaudit-0.22.2/auditors/seccomp/fix.gopackage seccomp import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" apiv1 "k8s.io/api/core/v1" ) type BySettingSeccompProfile struct { seccompProfileType apiv1.SeccompProfileType } func (pending *BySettingSeccompProfile) Plan() string { return fmt.Sprintf("Set SeccompProfile type to '%s' in pod SecurityContext", pending.seccompProfileType) } func (pending *BySettingSeccompProfile) Apply(resource k8s.Resource) []k8s.Resource { podSpec := k8s.GetPodSpec(resource) if podSpec.SecurityContext == nil { podSpec.SecurityContext = &apiv1.PodSecurityContext{} } podSpec.SecurityContext.SeccompProfile = &apiv1.SeccompProfile{Type: pending.seccompProfileType} return nil } type BySettingSeccompProfileInContainer struct { container *k8s.ContainerV1 seccompProfileType apiv1.SeccompProfileType } func (pending *BySettingSeccompProfileInContainer) Plan() string { return fmt.Sprintf("Set SeccompProfile type to '%s' in SecurityContext for container `%s`", pending.seccompProfileType, pending.container.Name) } func (pending *BySettingSeccompProfileInContainer) Apply(resource k8s.Resource) []k8s.Resource { if pending.container.SecurityContext == nil { pending.container.SecurityContext = &apiv1.SecurityContext{} } pending.container.SecurityContext.SeccompProfile = &apiv1.SeccompProfile{Type: pending.seccompProfileType} return nil } type ByRemovingSeccompProfileInContainer struct { container *k8s.ContainerV1 } func (pending *ByRemovingSeccompProfileInContainer) Plan() string { return fmt.Sprintf("Remove SeccompProfile in SecurityContext for container `%s`", pending.container.Name) } func (pending *ByRemovingSeccompProfileInContainer) Apply(resource k8s.Resource) []k8s.Resource { if pending.container.SecurityContext == nil { return nil } pending.container.SecurityContext.SeccompProfile = nil return nil } 070701000000D8000081A400000000000000000000000166C635DC00000C74000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/auditors/seccomp/fix_test.gopackage seccomp import ( "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" apiv1 "k8s.io/api/core/v1" ) const fixtureDir = "fixtures" const emptyProfile = apiv1.SeccompProfileType("EMPTY") const defaultProfile = apiv1.SeccompProfileTypeRuntimeDefault const localhostProfile = apiv1.SeccompProfileTypeLocalhost func TestFixSeccomp(t *testing.T) { cases := []struct { file string expectedPodSeccompProfile apiv1.SeccompProfileType expectedContainerSeccompProfiles []apiv1.SeccompProfileType }{ {"seccomp-profile-missing.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile}}, {"seccomp-profile-missing-disabled-container.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile}}, {"seccomp-profile-missing-annotations.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile}}, {"seccomp-disabled-pod.yml", defaultProfile, []apiv1.SeccompProfileType{defaultProfile}}, {"seccomp-disabled.yml", defaultProfile, []apiv1.SeccompProfileType{emptyProfile, emptyProfile}}, {"seccomp-disabled-localhost.yml", localhostProfile, []apiv1.SeccompProfileType{defaultProfile, emptyProfile}}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { resources, _ := test.FixSetup(t, fixtureDir, tc.file, New()) require.Len(t, resources, 1) resource := resources[0] updatedPodSpec := k8s.GetPodSpec(resource) checkPodSeccompProfile(t, updatedPodSpec, tc.expectedPodSeccompProfile) checkContainerSeccompProfiles(t, updatedPodSpec, tc.expectedContainerSeccompProfiles) checkNoSeccompAnnotations(t, resource) }) } } func checkPodSeccompProfile(t *testing.T, podSpec *apiv1.PodSpec, expectedPodSeccompProfile apiv1.SeccompProfileType) { securityContext := podSpec.SecurityContext if expectedPodSeccompProfile == emptyProfile { require.Nil(t, securityContext) } else { assert.Equal(t, expectedPodSeccompProfile, securityContext.SeccompProfile.Type) } } func checkContainerSeccompProfiles(t *testing.T, podSpec *apiv1.PodSpec, expectedContainerSeccompProfiles []apiv1.SeccompProfileType) { for i, container := range podSpec.Containers { securityContext := container.SecurityContext expectedProfile := expectedContainerSeccompProfiles[i] if expectedProfile == emptyProfile { require.True(t, securityContext == nil || securityContext.SeccompProfile == nil) } else { assert.Equal(t, expectedProfile, securityContext.SeccompProfile.Type) } } } func checkNoSeccompAnnotations(t *testing.T, resource k8s.Resource) { annotations := k8s.GetAnnotations(resource) if annotations == nil { return } seccompAnnotations := []string{} for annotation := range annotations { if annotation == PodAnnotationKey || strings.HasPrefix(annotation, ContainerAnnotationKeyPrefix) { seccompAnnotations = append(seccompAnnotations, annotation) } } assert.Empty(t, seccompAnnotations) } 070701000000D9000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/auditors/seccomp/fixtures070701000000DA000081A400000000000000000000000166C635DC0000017A000000000000000000000000000000000000004A00000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-disabled-localhost.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-disabled-localhost spec: securityContext: seccompProfile: type: Localhost localhostProfile: my-seccomp-profile.json containers: - name: container1 image: scratch securityContext: seccompProfile: type: Unconfined - name: container2 image: scratch 070701000000DB000081A400000000000000000000000166C635DC0000011C000000000000000000000000000000000000004400000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-disabled-pod.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-disabled-pod spec: securityContext: seccompProfile: type: Unconfined containers: - name: container image: scratch securityContext: seccompProfile: type: RuntimeDefault 070701000000DC000081A400000000000000000000000166C635DC00000145000000000000000000000000000000000000004000000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-disabled.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-disabled spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: container1 image: scratch securityContext: seccompProfile: type: Unconfined - name: container2 image: scratch 070701000000DD000081A400000000000000000000000166C635DC000000FC000000000000000000000000000000000000004300000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-enabled-pod.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-enabled-pod spec: securityContext: seccompProfile: type: Localhost localhostProfile: my-seccomp-profile.json containers: - name: container image: scratch 070701000000DE000081A400000000000000000000000166C635DC000000D9000000000000000000000000000000000000003F00000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-enabled.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-enabled spec: containers: - name: container image: scratch securityContext: seccompProfile: type: RuntimeDefault 070701000000DF000081A400000000000000000000000166C635DC00000138000000000000000000000000000000000000005300000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-profile-missing-annotations.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-profile-missing-annotations annotations: seccomp.security.alpha.kubernetes.io/pod: runtime/default container.seccomp.security.alpha.kubernetes.io/container: localhost/bla spec: containers: - name: container image: scratch 070701000000E0000081A400000000000000000000000166C635DC000000F0000000000000000000000000000000000000005A00000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-profile-missing-disabled-container.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-profile-missing-disabled-container spec: containers: - name: container image: scratch securityContext: seccompProfile: type: Unconfined 070701000000E1000081A400000000000000000000000166C635DC00000093000000000000000000000000000000000000004700000000kubeaudit-0.22.2/auditors/seccomp/fixtures/seccomp-profile-missing.ymlapiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-profile-missing spec: containers: - name: container image: scratch 070701000000E2000081A400000000000000000000000166C635DC00002233000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/auditors/seccomp/seccomp.gopackage seccomp import ( "fmt" "strings" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/fix" "github.com/Shopify/kubeaudit/pkg/k8s" apiv1 "k8s.io/api/core/v1" ) const Name = "seccomp" const ( // SeccompDeprecatedAnnotations occurs when deprecated seccomp annotations are present SeccompDeprecatedAnnotations = "SeccompDeprecatedAnnotations" // SeccompProfileMissing occurs when there are no seccomp profiles (pod nor container level) SeccompProfileMissing = "SeccompProfileMissing" // SeccompDisabledPod occurs when the pod-level seccomp profile is set to a value which disables seccomp SeccompDisabledPod = "SeccompDisabledPod" // SeccompDisabledContainer occurs when the container-level seccomp profile is set to a value which disables seccomp SeccompDisabledContainer = "SeccompDisabledContainer" ) const ( // ProfileRuntimeDefault represents the default seccomp profile used by container runtime ProfileRuntimeDefault = apiv1.SeccompProfileTypeRuntimeDefault // ProfileLocalhost represents the localhost seccomp profile used by container runtime ProfileLocalhost = apiv1.SeccompProfileTypeLocalhost // ContainerAnnotationKeyPrefix represents the key of a seccomp profile applied to one container of a pod ContainerAnnotationKeyPrefix = apiv1.SeccompContainerAnnotationKeyPrefix // PodAnnotationKey represents the key of a seccomp profile applied to all containers of a pod PodAnnotationKey = apiv1.SeccompPodAnnotationKey ) // Seccomp implements Auditable type Seccomp struct{} func New() *Seccomp { return &Seccomp{} } // Audit checks that Seccomp is enabled for all containers func (a *Seccomp) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { var auditResults []*kubeaudit.AuditResult annotationAuditResult := auditAnnotations(resource) auditResults = appendNotNil(auditResults, annotationAuditResult) auditResult := auditPod(resource) auditResults = appendNotNil(auditResults, auditResult) for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, resource) auditResults = appendNotNil(auditResults, auditResult) } return auditResults, nil } func appendNotNil(auditResults []*kubeaudit.AuditResult, auditResult *kubeaudit.AuditResult) []*kubeaudit.AuditResult { if auditResult != nil { return append(auditResults, auditResult) } return auditResults } func auditAnnotations(resource k8s.Resource) *kubeaudit.AuditResult { podSpec := k8s.GetPodSpec(resource) if podSpec == nil { return nil } // We check annotations only when seccomp profile is missing for both pod and all containers // This way we ensure that we're in Manifest mode. // In Local and Cluster mode Kubernetes automatically populates seccomp profile in Security context when seccomp annotations are provided. if !isPodSeccompProfileMissing(podSpec.SecurityContext) || !isSeccompProfileMissingForAllContainers(resource) { return nil } seccompAnnotations := findSeccompAnnotations(resource) if len(seccompAnnotations) > 0 { return &kubeaudit.AuditResult{ Auditor: Name, Rule: SeccompDeprecatedAnnotations, Severity: kubeaudit.Warn, Message: "Pod Seccomp annotations are deprecated. Seccomp profile should be added to the pod SecurityContext.", PendingFix: &fix.ByRemovingPodAnnotations{Keys: seccompAnnotations}, Metadata: kubeaudit.Metadata{"AnnotationKeys": strings.Join(seccompAnnotations, ", ")}, } } return nil } func auditPod(resource k8s.Resource) *kubeaudit.AuditResult { podSpec := k8s.GetPodSpec(resource) if podSpec == nil { return nil } if isPodSeccompProfileMissing(podSpec.SecurityContext) { // If all the containers have container-level seccomp profiles then we don't need a pod-level profile if isSeccompEnabledForAllContainers(resource) { return nil } return &kubeaudit.AuditResult{ Auditor: Name, Rule: SeccompProfileMissing, Severity: kubeaudit.Error, Message: "Pod Seccomp profile is missing. Seccomp profile should be added to the pod SecurityContext.", PendingFix: &BySettingSeccompProfile{seccompProfileType: ProfileRuntimeDefault}, Metadata: kubeaudit.Metadata{}, } } podSeccompProfileType := podSpec.SecurityContext.SeccompProfile.Type if !isSeccompEnabled(podSeccompProfileType) { return &kubeaudit.AuditResult{ Auditor: Name, Rule: SeccompDisabledPod, Severity: kubeaudit.Error, Message: fmt.Sprintf("Pod Seccomp profile is set to %s which disables Seccomp. It should be set to the `%s` or `%s`.", podSeccompProfileType, ProfileRuntimeDefault, ProfileLocalhost), PendingFix: &BySettingSeccompProfile{seccompProfileType: ProfileRuntimeDefault}, Metadata: kubeaudit.Metadata{"SeccompProfileType": string(podSeccompProfileType)}, } } return nil } func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudit.AuditResult { // Assume that the container will be covered by the pod-level seccomp profile. If there is no pod-level // seccomp profile, assume that it will be added as part of the pod seccomp profile audit / autofix if isContainerSeccompProfileMissing(container.SecurityContext) { return nil } containerSeccompProfile := container.SecurityContext.SeccompProfile.Type if !isSeccompEnabled(containerSeccompProfile) { // If the pod seccomp profile is set to Localhost, and the container seccomp profile is disabled, // then set the container seccomp profile to the default profile. // Otherwise, remove the container seccomp profile in favour of the pod profile. var pendingFix kubeaudit.PendingFix var msg string podSpec := k8s.GetPodSpec(resource) if isPodSeccompProfileMissing(podSpec.SecurityContext) || isSeccompProfileDefault(podSpec.SecurityContext.SeccompProfile.Type) { pendingFix = &ByRemovingSeccompProfileInContainer{container: container} msg = fmt.Sprintf("Container Seccomp profile is set to %s which disables Seccomp. It should be removed from the container SecurityContext, as the pod SeccompProfile is set.", containerSeccompProfile) } else { pendingFix = &BySettingSeccompProfileInContainer{container: container, seccompProfileType: ProfileRuntimeDefault} msg = fmt.Sprintf("Container Seccomp profile is set to %s which disables Seccomp. It should be set to the `%s` or `%s`.", containerSeccompProfile, ProfileRuntimeDefault, ProfileLocalhost) } return &kubeaudit.AuditResult{ Auditor: Name, Rule: SeccompDisabledContainer, Severity: kubeaudit.Error, Message: msg, PendingFix: pendingFix, Metadata: kubeaudit.Metadata{ "Container": container.Name, "SeccompProfileType": string(containerSeccompProfile), }, } } return nil } func isPodSeccompProfileMissing(securityContext *apiv1.PodSecurityContext) bool { return securityContext == nil || securityContext.SeccompProfile == nil } func isContainerSeccompProfileMissing(securityContext *apiv1.SecurityContext) bool { return securityContext == nil || securityContext.SeccompProfile == nil } // returns false if there is at least one container that is not covered by a container-level seccomp annotation func isSeccompEnabledForAllContainers(resource k8s.Resource) bool { for _, container := range k8s.GetContainers(resource) { securityContext := container.SecurityContext if isContainerSeccompProfileMissing(securityContext) { return false } containerSeccompProfileType := securityContext.SeccompProfile.Type if !isSeccompEnabled(containerSeccompProfileType) { return false } } return true } func isSeccompProfileMissingForAllContainers(resource k8s.Resource) bool { for _, container := range k8s.GetContainers(resource) { securityContext := container.SecurityContext if !isContainerSeccompProfileMissing(securityContext) { return false } } return true } func isSeccompEnabled(seccompProfileType apiv1.SeccompProfileType) bool { return isSeccompProfileDefault(seccompProfileType) || isSeccompProfileLocalhost(seccompProfileType) } func isSeccompProfileDefault(seccompProfileType apiv1.SeccompProfileType) bool { return seccompProfileType == apiv1.SeccompProfileTypeRuntimeDefault } func isSeccompProfileLocalhost(seccompProfileType apiv1.SeccompProfileType) bool { return seccompProfileType == apiv1.SeccompProfileTypeLocalhost } func findSeccompAnnotations(resource k8s.Resource) []string { annotations := k8s.GetAnnotations(resource) seccompAnnotations := []string{} for annotation := range annotations { if annotation == PodAnnotationKey || strings.HasPrefix(annotation, ContainerAnnotationKeyPrefix) { seccompAnnotations = append(seccompAnnotations, annotation) } } return seccompAnnotations } 070701000000E3000081A400000000000000000000000166C635DC0000050F000000000000000000000000000000000000003200000000kubeaudit-0.22.2/auditors/seccomp/seccomp_test.gopackage seccomp import ( "strings" "testing" "github.com/Shopify/kubeaudit/internal/test" ) func TestAuditSeccomp(t *testing.T) { cases := []struct { file string expectedErrors []string testLocalMode bool }{ {"seccomp-profile-missing.yml", []string{SeccompProfileMissing}, true}, {"seccomp-profile-missing-disabled-container.yml", []string{SeccompProfileMissing, SeccompDisabledContainer}, true}, {"seccomp-profile-missing-annotations.yml", []string{SeccompProfileMissing, SeccompDeprecatedAnnotations}, false}, {"seccomp-disabled-pod.yml", []string{SeccompDisabledPod}, true}, {"seccomp-disabled.yml", []string{SeccompDisabledContainer}, true}, {"seccomp-disabled-localhost.yml", []string{SeccompDisabledContainer}, true}, {"seccomp-enabled-pod.yml", nil, true}, {"seccomp-enabled.yml", nil, true}, } for _, tc := range cases { // This line is needed because of how scopes work with parallel tests (see https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) tc := tc t.Run(tc.file, func(t *testing.T) { t.Parallel() test.AuditManifest(t, fixtureDir, tc.file, New(), tc.expectedErrors) if tc.testLocalMode { test.AuditLocal(t, fixtureDir, tc.file, New(), strings.Split(tc.file, ".")[0], tc.expectedErrors) } }) } } 070701000000E4000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001700000000kubeaudit-0.22.2/build070701000000E5000081ED00000000000000000000000166C635DC0000026C000000000000000000000000000000000000002200000000kubeaudit-0.22.2/build/ldflags.sh#!/usr/bin/env bash set -eu -o pipefail VERSION=${VERSION:-$(git describe --abbrev=0 --broken)} COMMIT=${COMMIT:-$(git rev-parse --short HEAD)} # BSD date command doesn't support RFC3339/ISO8601 or subsecond precision. BUILDDATE=${BUILDDATE:-$(date -u -Ins 2> /dev/null || true)} BUILDDATE=${BUILDDATE:-$(date -u +%FT%T000000000%z)} new_ldflags="-X \"github.com/Shopify/kubeaudit/cmd.Version=${VERSION}\"" new_ldflags+=" -X \"github.com/Shopify/kubeaudit/cmd.Commit=${COMMIT}\"" new_ldflags+=" -X \"github.com/Shopify/kubeaudit/cmd.BuildDate=${BUILDDATE}\"" export LDFLAGS="$new_ldflags ${LDFLAGS:-}" echo "$LDFLAGS" 070701000000E6000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001500000000kubeaudit-0.22.2/cmd070701000000E7000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001E00000000kubeaudit-0.22.2/cmd/commands070701000000E8000081A400000000000000000000000166C635DC00000007000000000000000000000000000000000000002600000000kubeaudit-0.22.2/cmd/commands/VERSION0.22.2 070701000000E9000081A400000000000000000000000166C635DC0000091F000000000000000000000000000000000000002500000000kubeaudit-0.22.2/cmd/commands/all.gopackage commands import ( "os" "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/config" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var auditAllConfig struct { configFile string } func auditAll(cmd *cobra.Command, args []string) { conf := loadKubeAuditConfigFromFile(auditAllConfig.configFile) // Config options set via flags override the config file conf = setConfigFromFlags(cmd, conf) auditors, err := all.Auditors(conf) if err != nil { log.WithError(err).Fatal("Error creating auditors") } runAudit(auditors...)(cmd, args) } func setConfigFromFlags(cmd *cobra.Command, conf config.KubeauditConfig) config.KubeauditConfig { flagset := cmd.Flags() for _, item := range []struct { flag string flagVal string configVal *string }{ {imageFlagName, imageConfig.Image, &conf.AuditorConfig.Image.Image}, {limitCpuFlagName, limitsConfig.CPU, &conf.AuditorConfig.Limits.CPU}, {limitMemoryFlagName, limitsConfig.Memory, &conf.AuditorConfig.Limits.Memory}, } { if flagset.Changed(item.flag) { *item.configVal = item.flagVal } } if flagset.Changed(capsAddFlagName) { conf.AuditorConfig.Capabilities.AllowAddList = capabilitiesConfig.AllowAddList } if flagset.Changed(sensitivePathsFlagName) { conf.AuditorConfig.Mounts.SensitivePaths = mountsConfig.SensitivePaths } return conf } func loadKubeAuditConfigFromFile(configFile string) config.KubeauditConfig { if configFile == "" { return config.KubeauditConfig{} } reader, err := os.Open(configFile) if err != nil { log.WithError(err).Fatal("Unable to open config file ", configFile) } conf, err := config.New(reader) if err != nil { log.WithError(err).Fatal("Error parsing config file ", configFile) } return conf } var auditAllCmd = &cobra.Command{ Use: "all", Short: "Run all audits", Long: `Run all audits Example usage: kubeaudit all -f /path/to/yaml kubeaudit all -k /path/to/kubeaudit-config.yaml /path/to/yaml `, Run: auditAll, } func init() { RootCmd.AddCommand(auditAllCmd) auditAllCmd.Flags().StringVarP(&auditAllConfig.configFile, "kconfig", "k", "", "Path to kubeaudit config") // Set flags for the auditors that have them setImageFlags(auditAllCmd) setLimitsFlags(auditAllCmd) setCapabilitiesFlags(auditAllCmd) setPathsFlags(auditAllCmd) } 070701000000EA000081A400000000000000000000000166C635DC000001FC000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/cmd/commands/apparmor.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/spf13/cobra" ) var appArmorCmd = &cobra.Command{ Use: "apparmor", Short: "Audit containers running without AppArmor", Long: `This command determines which containers are running without AppArmor enabled. An ERROR result is generated when a container has AppArmor disabled or misconfigured. Example usage: kubeaudit apparmor`, Run: runAudit(apparmor.New()), } func init() { RootCmd.AddCommand(appArmorCmd) } 070701000000EB000081A400000000000000000000000166C635DC00000378000000000000000000000000000000000000002600000000kubeaudit-0.22.2/cmd/commands/asat.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/asat" "github.com/spf13/cobra" ) var asatCmd = &cobra.Command{ Use: "asat", Aliases: []string{"sat"}, Short: "Audit pods using an automatically mounted default service account", Long: `This command determines which pods are running with autoMountServiceAcccountToken = true (or nil) and using a default service account. An ERROR result is generated when a container matches one of the following: automountServiceAccountToken = true and serviceAccountName is blank (default service account) automountServiceAccountToken = nil (defaults to true) and serviceAccountName is blank (default service account) A WARN result is generated when a pod is found using the deprecated 'serviceAccount' field. Example usage: kubeaudit asat`, Run: runAudit(asat.New()), } func init() { RootCmd.AddCommand(asatCmd) } 070701000000EC000081A400000000000000000000000166C635DC0000076A000000000000000000000000000000000000002900000000kubeaudit-0.22.2/cmd/commands/autofix.gopackage commands import ( "io" "os" "github.com/Shopify/kubeaudit/auditors/all" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var autofixConfig struct { outFile string kubeauditConfigFile string } func autofix(cmd *cobra.Command, args []string) { conf := loadKubeAuditConfigFromFile(autofixConfig.kubeauditConfigFile) conf = setConfigFromFlags(cmd, conf) auditors, err := all.Auditors(conf) if err != nil { log.WithError(err).Fatal("Error creating auditors") } report := getReport(auditors...) var f io.Writer if autofixConfig.outFile != "" { f, err = os.Create(autofixConfig.outFile) if err != nil { log.WithError(err).Fatal("Error opening out file") } } else { f, err = os.OpenFile(rootConfig.manifest, os.O_WRONLY|os.O_TRUNC, 0755) if err != nil { log.WithError(err).Fatal("Error opening manifest file") } } err = report.Fix(f) if err != nil { log.WithError(err).Fatal("Error fixing manifest") } } var autofixCmd = &cobra.Command{ Use: "autofix", Short: "Automagically make a manifest secure", Long: `This command automatically fixes all identified security issues for a given manifest (ie. all ERROR results generated by 'kubeaudit all'). If no output file is specified using the -o flag, the source manifest will be modified. You can use the -k flag followed by the path to the kubeaudit config file to run fixes based on custom rules. Example usage: kubeaudit autofix -f /path/to/yaml kubeaudit autofix -f /path/to/yaml -o /path/for/fixed/yaml kubeaudit autofix -k /path/to/kubeaudit-config.yaml -f /path/to/yaml `, Run: autofix, } func init() { RootCmd.AddCommand(autofixCmd) autofixCmd.Flags().StringVarP(&autofixConfig.outFile, "outfile", "o", "", "File to write fixed manifest to") autofixCmd.Flags().StringVarP(&autofixConfig.kubeauditConfigFile, "kconfig", "k", "", "Path to kubeaudit config") } 070701000000ED000081A400000000000000000000000166C635DC000004E1000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/cmd/commands/capabilities.gopackage commands import ( "fmt" "github.com/Shopify/kubeaudit/auditors/capabilities" "github.com/spf13/cobra" ) var capabilitiesConfig capabilities.Config const capsAddFlagName = "allow-add-list" var capabilitiesCmd = &cobra.Command{ Use: "capabilities", Aliases: []string{"caps"}, Short: "Audit containers not dropping ALL capabilities", Long: fmt.Sprintf(`This command determines which pods either have capabilities added or not set to ALL: An ERROR result is generated when a pod does not have drop ALL specified or when a capability is added. In case you need specific capabilities you can add them with the '--allow-add-list' flag, so kubeaudit will not report errors. Example usage: kubeaudit capabilities kubeaudit capabilities --allow-add-list "%s"`, "CHOWN"), Run: func(cmd *cobra.Command, args []string) { runAudit(capabilities.New(capabilitiesConfig))(cmd, args) }, } func setCapabilitiesFlags(cmd *cobra.Command) { cmd.Flags().StringSliceVar(&capabilitiesConfig.AllowAddList, capsAddFlagName, capabilities.DefaultAllowAddList, "Comma separated list of added capabilities that can be ignored by kubeaudit reports") } func init() { RootCmd.AddCommand(capabilitiesCmd) setCapabilitiesFlags(capabilitiesCmd) } 070701000000EE000081A400000000000000000000000166C635DC000005F0000000000000000000000000000000000000003000000000kubeaudit-0.22.2/cmd/commands/deprecatedapis.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/deprecatedapis" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var deprecatedapisConfig deprecatedapis.Config const ( currentVersionFlagName = "current-k8s-version" targetedVersionFlagName = "targeted-k8s-version" ) var deprecatedapisCmd = &cobra.Command{ Use: "deprecatedapis", Short: "Audit resource API version deprecations", Long: `This command determines which resource is defined with a deprecated API version. An ERROR result is generated for API version not available in the targeted version A WARN result is generated for API version deprecated in the current version An INFO result is generated for API version not yet deprecated in the current version Example usage: kubeaudit deprecatedapis kubeaudit deprecatedapis --current-k8s-version 1.22 --targeted-k8s-version 1.24`, Run: func(cmd *cobra.Command, args []string) { auditor, err := deprecatedapis.New(deprecatedapisConfig) if err != nil { log.Fatal("failed to create deprecatedapis auditor") } runAudit(auditor)(cmd, args) }, } func setdeprecatedapisFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&deprecatedapisConfig.CurrentVersion, currentVersionFlagName, "", "Kubernetes current version (eg 1.22)") cmd.Flags().StringVar(&deprecatedapisConfig.TargetedVersion, targetedVersionFlagName, "", "Kubernetes version to migrate to (eg 1.24)") } func init() { RootCmd.AddCommand(deprecatedapisCmd) setdeprecatedapisFlags(deprecatedapisCmd) } 070701000000EF000081A400000000000000000000000166C635DC0000024E000000000000000000000000000000000000002800000000kubeaudit-0.22.2/cmd/commands/hostns.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/hostns" "github.com/spf13/cobra" ) var hostnsCmd = &cobra.Command{ Use: "hostns", Aliases: []string{"namespaces"}, Short: "Audit pods with hostNetwork, hostIPC or hostPID enabled", Long: `This command determines which pods are running with hostNetwork, hostIPC or hostPID set to 'true'. An ERROR result is generated when a pod has at least one of hostNetwork, hostIPC or hostPID set to 'true'. Example usage: kubeaudit hostns`, Run: runAudit(hostns.New()), } func init() { RootCmd.AddCommand(hostnsCmd) } 070701000000F0000081A400000000000000000000000166C635DC000003F0000000000000000000000000000000000000002700000000kubeaudit-0.22.2/cmd/commands/image.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/image" "github.com/spf13/cobra" ) var imageConfig image.Config const imageFlagName = "image" var imageCmd = &cobra.Command{ Use: "image", Short: "Audit containers not using a specified image:tag", Long: `This command audits a container against a given image:tag. An ERROR result is generated when a container does not match the image:tag An INFO result is generated when a container has a matching image:tag. This command is also a root command, check 'kubeaudit image --help'. Example usage: kubeaudit image --image gcr.io/google_containers/echoserver:1.7 kubeaudit image -i gcr.io/google_containers/echoserver:1.7`, Run: func(cmd *cobra.Command, args []string) { runAudit(image.New(imageConfig))(cmd, args) }, } func setImageFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&imageConfig.Image, imageFlagName, "i", "", "Image to check against") } func init() { RootCmd.AddCommand(imageCmd) setImageFlags(imageCmd) } 070701000000F1000081A400000000000000000000000166C635DC000004DB000000000000000000000000000000000000002800000000kubeaudit-0.22.2/cmd/commands/limits.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/limits" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var limitsConfig limits.Config const ( limitMemoryFlagName = "memory" limitCpuFlagName = "cpu" ) var limitsCmd = &cobra.Command{ Use: "limits", Short: "Audit containers exceeding a specified CPU or memory limit", Long: `This command determines which containers exceed the specified CPU and memory limits, or have no limits configured. A WARN result is generated for each of the following cases: - The CPU limit is unset or exceeds the specified CPU limit - The memory limit is unset or exceeds the specified memory limit Example usage: kubeaudit limits kubeaudit limits --cpu 500m --memory 256Mi`, Run: func(cmd *cobra.Command, args []string) { auditor, err := limits.New(limitsConfig) if err != nil { log.Fatal("failed to create limits auditor") } runAudit(auditor)(cmd, args) }, } func setLimitsFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&limitsConfig.CPU, limitCpuFlagName, "", "Max CPU limit") cmd.Flags().StringVar(&limitsConfig.Memory, limitMemoryFlagName, "", "Max memory limit") } func init() { RootCmd.AddCommand(limitsCmd) setLimitsFlags(limitsCmd) } 070701000000F2000081A400000000000000000000000166C635DC00000518000000000000000000000000000000000000002800000000kubeaudit-0.22.2/cmd/commands/mounts.gopackage commands import ( "bytes" "fmt" "github.com/Shopify/kubeaudit/auditors/mounts" "github.com/spf13/cobra" "strings" ) const sensitivePathsFlagName = "denyPathsList" var mountsConfig mounts.Config var mountsCmd = &cobra.Command{ Use: "mounts", Short: "Audit containers that mount sensitive paths", Long: fmt.Sprintf(`This command determines which containers mount sensitive host paths. If no paths list is provided, the following paths are used: %s A WARN result is generated when a container mounts one or more paths specified with the '--denyPathsList' argument. Example usage: kubeaudit mounts --denyPathsList "%s"`, formatPathsList(), strings.Join(mounts.DefaultSensitivePaths[:3], ",")), Run: func(cmd *cobra.Command, args []string) { runAudit(mounts.New(mountsConfig))(cmd, args) }, } func init() { RootCmd.AddCommand(mountsCmd) setPathsFlags(mountsCmd) } func setPathsFlags(cmd *cobra.Command) { cmd.Flags().StringSliceVarP(&mountsConfig.SensitivePaths, sensitivePathsFlagName, "d", mounts.DefaultSensitivePaths, "List of sensitive paths that shouldn't be mounted") } func formatPathsList() string { var buffer bytes.Buffer for _, path := range mounts.DefaultSensitivePaths { buffer.WriteString("\n- ") buffer.WriteString(path) } return buffer.String() } 070701000000F3000081A400000000000000000000000166C635DC0000035C000000000000000000000000000000000000002900000000kubeaudit-0.22.2/cmd/commands/netpols.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/netpols" "github.com/spf13/cobra" ) var netpolsCmd = &cobra.Command{ Use: "netpols", Aliases: []string{"np"}, Short: "Audit namespaces that do not have a default deny network policy", Long: `This command determines which namespaces do not have a default deny NetworkPolicy. An ERROR result is generated for each of the followign cases: - A namespace does not have a default deny-all-ingress NetworkPolicy - A namespace does not have a default deny-all-egress NetworkPolicy A WARN result is generated for each of the following cases: - A namespace has a default allow-all-ingress NetworkPolicy - A namespace has a default allow-all-egress NetworkPolicy Example usage: kubeaudit netpols`, Run: runAudit(netpols.New()), } func init() { RootCmd.AddCommand(netpolsCmd) } 070701000000F4000081A400000000000000000000000166C635DC0000027B000000000000000000000000000000000000002900000000kubeaudit-0.22.2/cmd/commands/nonroot.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/nonroot" "github.com/spf13/cobra" ) var runAsNonRootCmd = &cobra.Command{ Use: "nonroot", Short: "Audit containers allowing for root user", Long: `This command determines which containers are allowed to run as root (uid=0). An ERROR result is generated when container does not have 'runAsNonRoot = true' or if a root user (UID 0) is explicitly set using 'runAsUser' in either its container SecurityContext or its pod SecurityContext. Example usage: kubeaudit nonroot`, Run: runAudit(nonroot.New()), } func init() { RootCmd.AddCommand(runAsNonRootCmd) } 070701000000F5000081A400000000000000000000000166C635DC0000025B000000000000000000000000000000000000002900000000kubeaudit-0.22.2/cmd/commands/privesc.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/privesc" "github.com/spf13/cobra" ) var allowPrivilegeEscalationCmd = &cobra.Command{ Use: "privesc", Aliases: []string{"allowpe"}, Short: "Audit containers that allow privilege escalation", Long: `This command determines which containers allow privilege escalation. An ERROR result is generated when a container does not have 'allowPrivilegeEscalation = false' in its SecurityContext. Example usage: kubeaudit privesc`, Run: runAudit(privesc.New()), } func init() { RootCmd.AddCommand(allowPrivilegeEscalationCmd) } 070701000000F6000081A400000000000000000000000166C635DC000002D6000000000000000000000000000000000000002C00000000kubeaudit-0.22.2/cmd/commands/privileged.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/privileged" "github.com/spf13/cobra" ) var privilegedCmd = &cobra.Command{ Use: "privileged", Aliases: []string{"priv"}, Short: "Audit containers running as privileged", Long: `This command determines which containers are running as privileged. An ERROR result is generated when a container has 'privileged = true' in its SecurityContext. A WARN result is generated a when a container has 'privileged = nil' in its SecurityContext. 'privileged' defaults to 'true' so this is ok, but it should be explicitly set to 'true'. Example usage: kubeaudit priv`, Run: runAudit(privileged.New()), } func init() { RootCmd.AddCommand(privilegedCmd) } 070701000000F7000081A400000000000000000000000166C635DC00001D0B000000000000000000000000000000000000002600000000kubeaudit-0.22.2/cmd/commands/root.gopackage commands import ( "fmt" "os" "strings" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" apiv1 "k8s.io/api/core/v1" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/config" "github.com/Shopify/kubeaudit/internal/color" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/internal/sarif" ) var rootConfig rootFlags type rootFlags struct { format string kubeConfig string context string manifest string namespace string minSeverity string exitCode int includeGenerated bool noColor bool } // RootCmd defines the shell command usage for kubeaudit. var RootCmd = &cobra.Command{ Use: "kubeaudit", Short: "A Kubernetes security auditor", Long: `🚨 Deprecation Notice 🚨 Kubeaudit is planned for deprecation by October 2024. We are actively seeking maintainers who are interested in taking over the stewardship of this project. If you are passionate about continuing its development and maintenance, please reach out to us. For users looking for alternatives, we recommend transitioning to Kubebench, which offers similar functionality and is actively maintained. Thank you to the community for your contributions and support. -------------------------------------------------------------------- Kubeaudit audits Kubernetes clusters for common security controls. kubeaudit has three modes: 1. Manifest mode: If a Kubernetes manifest file is provided using the -f/--manifest flag, kubeaudit will audit the manifest file. Kubeaudit also supports autofixing in manifest mode using the 'autofix' command. This will fix the manifest in-place. The fixed manifest can be written to a different file using the -o/--out flag. 2. Cluster mode: If kubeaudit detects it is running in a cluster, it will audit the other resources in the cluster. 3. Local mode: kubeaudit will try to connect to a cluster using the local kubeconfig file ($HOME/.kube/config). A different kubeconfig location can be specified using the -c/--kubeconfig flag `, } // Execute is a wrapper for the RootCmd.Execute method which will exit the program if there is an error. func Execute() { if err := RootCmd.Execute(); err != nil { log.Fatal(err) } } func init() { RootCmd.PersistentFlags().StringVarP(&rootConfig.kubeConfig, "kubeconfig", "", "", "Path to local Kubernetes config file. Only used in local mode (default is $HOME/.kube/config)") RootCmd.PersistentFlags().StringVarP(&rootConfig.context, "context", "c", "", "The name of the kubeconfig context to use") RootCmd.PersistentFlags().StringVarP(&rootConfig.minSeverity, "minseverity", "m", "info", "Set the lowest severity level to report (one of \"error\", \"warning\", \"info\")") RootCmd.PersistentFlags().StringVarP(&rootConfig.format, "format", "p", "pretty", "The output format to use (one of \"sarif\",\"pretty\", \"logrus\", \"json\")") RootCmd.PersistentFlags().StringVarP(&rootConfig.namespace, "namespace", "n", apiv1.NamespaceAll, "Only audit resources in the specified namespace. Not currently supported in manifest mode.") RootCmd.PersistentFlags().BoolVarP(&rootConfig.includeGenerated, "includegenerated", "g", false, "Include generated resources in scan (eg. pods generated by deployments).") RootCmd.PersistentFlags().BoolVar(&rootConfig.noColor, "no-color", false, "Don't produce colored output.") RootCmd.PersistentFlags().StringVarP(&rootConfig.manifest, "manifest", "f", "", "Path to the yaml configuration to audit. Only used in manifest mode.") RootCmd.PersistentFlags().IntVarP(&rootConfig.exitCode, "exitcode", "e", 2, "Exit code to use if there are results with severity of \"error\". Conventionally, 0 is used for success and all non-zero codes for an error.") } // KubeauditLogLevels represents an enum for the supported log levels. var KubeauditLogLevels = map[string]kubeaudit.SeverityLevel{ "error": kubeaudit.Error, "warn": kubeaudit.Warn, "warning": kubeaudit.Warn, "info": kubeaudit.Info, } func runAudit(auditable ...kubeaudit.Auditable) func(cmd *cobra.Command, args []string) { return func(cmd *cobra.Command, args []string) { report := getReport(auditable...) fmt.Fprintln(os.Stderr, color.Yellow("\n[Deprecation Notice]: Kubeaudit is planned for deprecation by October 2024.\nWe are actively seeking maintainers who are interested in taking over the stewardship of this project. If you are passionate about continuing its development and maintenance, please reach out to us.\nFor users looking for alternatives, we recommend transitioning to Kubebench, which offers similar functionality and is actively maintained.\nThank you to the community for your contributions and support.")) fmt.Fprintln(os.Stderr, color.Yellow("\n[WARNING]: kubernetes.io for override labels will soon be deprecated. Please, update them to use kubeaudit.io instead.")) printOptions := []kubeaudit.PrintOption{ kubeaudit.WithMinSeverity(KubeauditLogLevels[strings.ToLower(rootConfig.minSeverity)]), kubeaudit.WithColor(!rootConfig.noColor), } switch rootConfig.format { case "sarif": sarifReport, err := sarif.Create(report) if err != nil { log.WithError(err).Fatal("Error generating the SARIF output") } if err := sarifReport.PrettyWrite(os.Stdout); err != nil { log.WithError(err).Fatal("Error executing SARIF PrettyWrite") } if report.HasErrors() { os.Exit(rootConfig.exitCode) } return case "json": printOptions = append(printOptions, kubeaudit.WithFormatter(&log.JSONFormatter{})) case "logrus": printOptions = append(printOptions, kubeaudit.WithFormatter(&log.TextFormatter{})) } report.PrintResults(printOptions...) if report.HasErrors() { os.Exit(rootConfig.exitCode) } } } func getReport(auditors ...kubeaudit.Auditable) *kubeaudit.Report { auditor := initKubeaudit(auditors...) if rootConfig.manifest != "" { var f *os.File if rootConfig.manifest == "-" { f = os.Stdin rootConfig.manifest = "" } else { manifest, err := os.Open(rootConfig.manifest) if err != nil { log.WithError(err).Fatal("Error opening manifest file") } f = manifest } report, err := auditor.AuditManifest(rootConfig.manifest, f) if err != nil { log.WithError(err).Fatal("Error auditing manifest") } return report } if k8sinternal.IsRunningInCluster(k8sinternal.DefaultClient) && rootConfig.kubeConfig == "" { report, err := auditor.AuditCluster(k8sinternal.ClientOptions{Namespace: rootConfig.namespace, IncludeGenerated: rootConfig.includeGenerated}) if err != nil { log.WithError(err).Fatal("Error auditing cluster") } return report } report, err := auditor.AuditLocal(rootConfig.kubeConfig, rootConfig.context, kubeaudit.AuditOptions{Namespace: rootConfig.namespace, IncludeGenerated: rootConfig.includeGenerated}) if err != nil { log.WithError(err).Fatal("Error auditing cluster in local mode") } return report } func initKubeaudit(auditable ...kubeaudit.Auditable) *kubeaudit.Kubeaudit { if len(auditable) == 0 { allAuditors, err := all.Auditors(config.KubeauditConfig{}) if err != nil { log.WithError(err).Fatal("Error initializing auditors") } auditable = allAuditors } auditor, err := kubeaudit.New(auditable) if err != nil { log.WithError(err).Fatal("Error creating auditor") } return auditor } 070701000000F8000081A400000000000000000000000166C635DC00000228000000000000000000000000000000000000002800000000kubeaudit-0.22.2/cmd/commands/rootfs.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/rootfs" "github.com/spf13/cobra" ) var readonlyfsCmd = &cobra.Command{ Use: "rootfs", Short: "Audit containers not using a read only root filesystems", Long: `This command determines which containers do not have a read only root file system. An ERROR result is generated when a container does not have 'readOnlyRootFilesystem = true' in its SecurityContext. Example usage: kubeaudit rootfs`, Run: runAudit(rootfs.New()), } func init() { RootCmd.AddCommand(readonlyfsCmd) } 070701000000F9000081A400000000000000000000000166C635DC000001F3000000000000000000000000000000000000002900000000kubeaudit-0.22.2/cmd/commands/seccomp.gopackage commands import ( "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/spf13/cobra" ) var seccompCmd = &cobra.Command{ Use: "seccomp", Short: "Audit containers running without Seccomp", Long: `This command determines which containers are running without Seccomp enabled. An ERROR result is generated when a container has Seccomp disabled or misconfigured. Example usage: kubeaudit seccomp`, Run: runAudit(seccomp.New()), } func init() { RootCmd.AddCommand(seccompCmd) } 070701000000FA000081A400000000000000000000000166C635DC00000172000000000000000000000000000000000000002900000000kubeaudit-0.22.2/cmd/commands/version.gopackage commands import ( _ "embed" "fmt" "strings" "github.com/spf13/cobra" ) //go:embed VERSION var version string var versionCmd = &cobra.Command{ Use: "version", Short: "Prints the current kubeaudit version", Run: func(cmd *cobra.Command, args []string) { fmt.Println(strings.TrimSpace(version)) }, } func init() { RootCmd.AddCommand(versionCmd) } 070701000000FB000081A400000000000000000000000166C635DC00000066000000000000000000000000000000000000001D00000000kubeaudit-0.22.2/cmd/main.gopackage main import "github.com/Shopify/kubeaudit/cmd/commands" func main() { commands.Execute() } 070701000000FC000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001800000000kubeaudit-0.22.2/config070701000000FD000081A400000000000000000000000166C635DC00000545000000000000000000000000000000000000002200000000kubeaudit-0.22.2/config/config.gopackage config import ( "io" "github.com/Shopify/kubeaudit/auditors/deprecatedapis" "github.com/Shopify/kubeaudit/auditors/mounts" "github.com/Shopify/kubeaudit/auditors/capabilities" "github.com/Shopify/kubeaudit/auditors/image" "github.com/Shopify/kubeaudit/auditors/limits" "gopkg.in/yaml.v3" ) func New(configData io.Reader) (KubeauditConfig, error) { configBytes, err := io.ReadAll(configData) if err != nil { return KubeauditConfig{}, err } config := KubeauditConfig{} err = yaml.Unmarshal(configBytes, &config) if err != nil { return KubeauditConfig{}, err } return config, nil } type KubeauditConfig struct { EnabledAuditors map[string]bool `yaml:"enabledAuditors"` AuditorConfig AuditorConfig `yaml:"auditors"` } func (conf *KubeauditConfig) GetEnabledAuditors() map[string]bool { if conf == nil { return map[string]bool{} } return conf.EnabledAuditors } func (conf *KubeauditConfig) GetAuditorConfigs() AuditorConfig { if conf == nil { return AuditorConfig{} } return conf.AuditorConfig } type AuditorConfig struct { Capabilities capabilities.Config `yaml:"capabilities"` DeprecatedAPIs deprecatedapis.Config `yaml:"config"` Image image.Config `yaml:"image"` Limits limits.Config `yaml:"limits"` Mounts mounts.Config `yaml:"mounts"` } 070701000000FE000081A400000000000000000000000166C635DC000003CA000000000000000000000000000000000000002400000000kubeaudit-0.22.2/config/config.yaml# Sample config enabledAuditors: # Auditors are enabled by default if they are not explicitly set to "false" apparmor: true asat: true capabilities: true deprecatedapis: true hostns: true image: true limits: true mounts: true netpols: true nonroot: true privesc: true privileged: true rootfs: true seccomp: true auditors: capabilities: # add capabilities needed to the add list, so kubeaudit won't report errors add: ["AUDIT_WRITE", "CHOWN", "KILL"] deprecatedapis: currentVersion: "1.22" targetedVersion: "1.25" image: image: "myimage:mytag" limits: cpu: "750m" memory: "500m" mounts: denyPathsList: ["/proc", "/var/run/docker.sock", "/", "/etc", "/root", "/var/run/crio/crio.sock", "/run/containerd/containerd.sock", /home/admin", "/var/lib/kubelet", "/var/lib/kubelet/pki", "/etc/kubernetes", "/etc/kubernetes/manifests"] 070701000000FF000081A400000000000000000000000166C635DC00000228000000000000000000000000000000000000002700000000kubeaudit-0.22.2/config/config_test.gopackage config_test import ( "os" "testing" "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Test that the sample config includes all auditors func TestConfig(t *testing.T) { configFile := "config.yaml" reader, err := os.Open(configFile) require.NoError(t, err) conf, err := config.New(reader) require.NoError(t, err) assert.Equal(t, len(all.AuditorNames), len(conf.GetEnabledAuditors()), "Config is missing auditors") } 07070100000100000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001600000000kubeaudit-0.22.2/docs07070100000101000081A400000000000000000000000166C635DC000011BB000000000000000000000000000000000000001D00000000kubeaudit-0.22.2/docs/all.md# All Auditor (all) Runs all available auditors, or those specified using a kubeaudit config. ## General Usage ``` kubeaudit all [flags] ``` ## Flags | Short | Long | Description | Default | | :---- | :-------- | :----------------------- | :------ | | -k | --kconfig | Path to kubeaudit config | | Also see [Global Flags](/README.md#global-flags) ### Kubeaudit Config A kubeaudit config file can be used instead of flags. ``` kubeaudit all -k "/path/to/kubeaudit-config.yml" -f "/path/to/manifest.yml" ``` Also see [Configuration File](/README.md#configuration-file) ## Examples ``` $ kubeaudit all -f "internal/test/fixtures/all_resources/deployment-apps-v1.yml" ---------------- Results for --------------- apiVersion: v1 kind: Namespace metadata: name: deployment-apps-v1 -------------------------------------------- -- [error] MissingDefaultDenyIngressAndEgressNetworkPolicy Message: Namespace is missing a default deny ingress and egress NetworkPolicy. Metadata: Namespace: deployment-apps-v1 ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: deployment-apps-v1 -------------------------------------------- -- [error] AppArmorAnnotationMissing Message: AppArmor annotation missing. The annotation 'container.apparmor.security.beta.kubernetes.io/container' should be added. Metadata: Container: container MissingAnnotation: container.apparmor.security.beta.kubernetes.io/container -- [error] AutomountServiceAccountTokenTrueAndDefaultSA Message: Default service account with token mounted. automountServiceAccountToken should be set to 'false' or a non-default service account should be used. -- [error] CapabilityOrSecurityContextMissing Message: Security Context not set. The Security Context should be specified and all Capabilities should be dropped by setting the Drop list to ALL. Metadata: Container: container -- [error] NamespaceHostNetworkTrue Message: hostNetwork is set to 'true' in PodSpec. It should be set to 'false'. Metadata: PodHost: -- [error] NamespaceHostIPCTrue Message: hostIPC is set to 'true' in PodSpec. It should be set to 'false'. Metadata: PodHost: -- [error] NamespaceHostPIDTrue Message: hostPID is set to 'true' in PodSpec. It should be set to 'false'. Metadata: PodHost: -- [warning] ImageTagMissing Message: Image tag is missing. Metadata: Container: container -- [warning] LimitsNotSet Message: Resource limits not set. Metadata: Container: container -- [error] RunAsNonRootPSCNilCSCNil Message: runAsNonRoot is not set in container SecurityContext nor the PodSecurityContext. It should be set to 'true' in at least one of the two. Metadata: Container: container -- [error] AllowPrivilegeEscalationNil Message: allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'. Metadata: Container: container -- [warning] PrivilegedNil Message: privileged is not set in container SecurityContext. Privileged defaults to 'false' but it should be explicitly set to 'false'. Metadata: Container: container -- [error] ReadOnlyRootFilesystemNil Message: readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'. Metadata: Container: container -- [error] SeccompProfileMissing Message: Pod Seccomp profile is missing. Seccomp profile should be added to the pod SecurityContext. ``` ### Example with Kubeaudit Config Consider the following kubeaudit config `config.yaml` ```yaml enabledAuditors: # Auditors are enabled by default if they are not explicitly set to "false" hostns: false image: false auditors: capabilities: add: - AUDIT_WRITE - CHOWN ``` The config can be passed to the `all` command using the `-k/--kconfig` flag: ``` $ kubeaudit all -k "config.yaml" -f "auditors/all/fixtures/audit_all_v1.yml" ``` ### Example with Flags The behaviour of the `all` command can also be customized by using flags. The `all` command supports all flags supported by individual auditors (see the individual [auditor docs](/README.md#auditors) for all the flags). For example, we can use the `--memory` flag (supported by the `limits` auditor): ``` kubeaudit all -f "manifest.yml" --memory 200 ``` Here, if the memory specified is higher than 200, `kubeaudit` will report that the memory limit was exceeded. 07070100000102000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001F00000000kubeaudit-0.22.2/docs/auditors07070100000103000081A400000000000000000000000166C635DC00000C0D000000000000000000000000000000000000002B00000000kubeaudit-0.22.2/docs/auditors/apparmor.md# AppArmor Auditor (apparmor) Finds containers that do not have AppArmor enabled. ## General Usage ``` kubeaudit apparmor [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit apparmor -f "auditors/apparmor/fixtures/apparmor-annotation-missing.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-annotation-missing -------------------------------------------- -- [error] AppArmorAnnotationMissing Message: AppArmor annotation missing. The annotation 'container.apparmor.security.beta.kubernetes.io/container' should be added. Metadata: MissingAnnotation: container.apparmor.security.beta.kubernetes.io/container Container: container ``` If an apparmor annotation refers to a container which doesn't exist, `kubectl apply` will fail. Kubeaudit produces an error for this case: ``` $ kubeaudit apparmor -f "auditors/apparmor/fixtures/apparmor-invalid-annotation.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod namespace: apparmor-enabled -------------------------------------------- -- [error] AppArmorInvalidAnnotation Message: AppArmor annotation key refers to a container that doesn't exist. Remove the annotation 'container.apparmor.security.beta.kubernetes.io/container2: runtime/default'. Metadata: Container: container2 Annotation: container.apparmor.security.beta.kubernetes.io/container2: runtime/default ``` ## Explanation AppArmor is a Mandatory Access Control (MAC) system used by Linux. AppArmor is enabled by adding `container.apparmor.security.beta.kubernetes.io/[container name]` as a pod-level annotation and setting its value to either `runtime/default` or a profile (`localhost/[profile name]`). Example of a resource which passes the `apparmor` audit: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: annotations: container.apparmor.security.beta.kubernetes.io/myContainer: runtime/default spec: containers: - name: myContainer ``` To learn more about AppArmor, see https://wiki.ubuntu.com/AppArmor To learn more about AppArmor in Kubernetes, see https://kubernetes.io/docs/tutorials/clusters/apparmor/#securing-a-pod ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). Override identifier for the `unconfined` apparmor profile value: `allow-disabled-apparmor` Container overrides have the form: ```yaml container.kubeaudit.io/[container name].allow-disabled-apparmor: "SomeReason" ``` Example of resource with the `unconfined` apparmor profile overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: annotations: container.apparmor.security.beta.kubernetes.io/myContainer: unconfined labels: container.kubeaudit.io/myContainer.allow-disabled-apparmor: "SomeReason" spec: containers: - name: myContainer image: scratch ``` 07070100000104000081A400000000000000000000000166C635DC00000D0B000000000000000000000000000000000000002700000000kubeaudit-0.22.2/docs/auditors/asat.md# automountServiceAccountToken Auditor (asat) Finds containers that meet either of the following conditions: 1. The deprecated `serviceAccount` field is used 1. The default service account is automatically mounted ## General Usage ``` kubeaudit asat [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` kubeaudit asat -f "auditors/asat/fixtures/service-account-token-true-and-no-name.yml" ---------------- Results for --------------- apiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: service-account-token-true-and-no-name -------------------------------------------- -- [error] AutomountServiceAccountTokenTrueAndDefaultSA Message: Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used. ``` ## Explanation `serviceAccount` is a deprecated field. `serviceAccountName` should be used instead. Example of a resource which fails the `asat` check because it uses `serviceAccount`: ```yaml apiVersion: v1 kind: Deployment spec: template: spec: serviceAccount: ThisFieldIsDeprecated containers: - name: myContainer ``` Automounting a default service account would allow any compromised pod to run API commands against the cluster. Either automounting should be disabled or a non-default service account with sane permissions should be used. To make sure a non-default service account is used, `serviceAccountName` must be set to a value other than `default`. To make sure a service account is not automatically mounted, `automountServiceAccountToken` must be explicitly set to `false` (it defaults to `true`) on either the ServiceAccount (for kubernetes 1.6+) or on the PodSpec. Example of disabling `automountServiceAccountToken` on the default ServiceAccount (kubernetes 1.6+): ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: default automountServiceAccountToken: false ``` Example of disabling `automountServiceAccountToken` on the PodSpec of a Deployment: ```yaml apiVersion: v1 kind: Deployment spec: template: spec: automountServiceAccountToken: false containers: - name: myContainer ``` Note that if `automountServiceAccountToken` is set on the PodSpec, this will take precedence over `automountServiceAccountToken` set on the ServiceAccount, so you should never set `automountServiceAccountToken: true` in the PodSpec when using the default ServiceAccount. Example of using a non-default service account: ```yaml apiVersion: v1 kind: Deployment spec: template: spec: serviceAccountName: customServiceAccount containers: - name: myContainer ``` ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). Override identifier: `allow-automount-service-account-token` Only pod overrides are supported: ```yaml kubeaudit.io/allow-automount-service-account-token: "" ``` Example of a resource with `asat` results overridden: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: kubeaudit.io/allow-automount-service-account-token: "" spec: automountServiceAccountToken: true containers: - name: myContainer ``` 07070100000105000081A400000000000000000000000166C635DC00001B07000000000000000000000000000000000000002F00000000kubeaudit-0.22.2/docs/auditors/capabilities.md# Capabilities Auditor (capabilities) Finds containers that do not drop the recommended capabilities or add new ones. ## General Usage ``` kubeaudit capabilities [flags] ``` ### Flags | Flag | Description | | :---- | :---------------------------------------------------------------------------------- | | --allow-add-list | Comma separated list of added capabilities that can be ignored by kubeaudit reports | Also see [Global Flags](/README.md#global-flags) ## Examples ```shell $ kubeaudit capabilities -f "auditors/capabilities/fixtures/capabilities-nil.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-nil -------------------------------------------- -- [error] CapabilityOrSecurityContextMissing Message: Security Context not set. The Security Context should be specified and all Capabilities should be dropped by setting the Drop list to ALL. Metadata: Container: container ``` ### Example with Config File A custom Add list can be provided in the config file. See [docs](docs/all.md) for more information. These are the capabilities you'd like to add and not have kubeaudit raise an error. In this example, kubeaudit will only error for "CHOWN" because it wasn't added to the add list in the config. `config.yaml` ```yaml --- auditors: capabilities: # add capabilities needed to the add list, so kubeaudit won't report errors allowAddList: ['KILL', 'MKNOD'] ``` `manifest.yaml` ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: example-namespace spec: template: spec: containers: - name: container1 image: scratch securityContext: capabilities: add: - CHOWN - KILL - MKNOD drop: - ALL ``` ```shell $ kubeaudit all --kconfig "config.yaml" -f "manifest.yaml" ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: capabilities-some-allowed-multi-containers-some-labels -------------------------------------------- -- [error] CapabilityAdded Message: Capability "CHOWN" added. It should be removed from the capability add list. If you need this capability, add an override label such as'container.kubeaudit.io/container1.allow-capability-chown: SomeReason'. Metadata: Container: container1 ``` **Note**: if using http://man7.org/linux/man-pages/man7/capabilities.7.html as a reference for capability names, drop the `CAP_` prefix. ### Example with Custom Add List A custom add list can be provided as a comma separated value list of capabilities using the `--allow-add-list` flag. These are the capabilities you'd like to add and not have kubeaudit raise an error: `manifest.yaml` (example manifest) ```yaml capabilities: add: - CHOWN - KILL - MKNOD - NET_ADMIN ``` Here we're only adding 3 capabilities to the add list to be ignored. Since we didn't add `NET_ADMIN` to the list, kubeaudit will raise an error for this one. ```shell $ kubeaudit capabilities --allow-add-list "CHOWN,KILL,MKNOD" -f "manifest.yaml" ---------------- Results for --------------- apiVersion: apps/v1beta2 kind: Deployment metadata: name: deployment namespace: example-namespace -------------------------------------------- -- [error] CapabilityAdded Message: Capability "NET_ADMIN" added. It should be removed from the capability add list. If you need this capability, add an override label such as 'container.kubeaudit.io/container1.allow-capability-net-admin: SomeReason'. Metadata: Container: container1 Capabiliy: NET_ADMIN exit status 2 ``` ## Explanation Capabilities (specifically, Linux capabilities), are used for permission management in Linux. Some capabilities are enabled by default. Ideally, all capabilities should be dropped: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer securityContext: capabilities: drop: - ALL ``` If capabiltiies are required, only those required capabilities should be added: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer securityContext: capabilities: drop: - all add: - AUDIT_WRITE ``` In this case, an override label needs to be added to tell kubeaudit that the capability was added on purpose. See [Override Errors](#override-errors). To learn more about capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html To learn more about capabilities in Kubernetes, see https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). The override identifier has the format `allow-capability-[capability]` which allows for each capability to be individually overridden. To turn a capability name into an override identifier do the following: 1. Lowercase the capability name 1. Replace underscores (`_`) with dashes (`-`) 1. Prepend `allow-capability-` For example, the override identifier for the `AUDIT_WRITE` capability would be `allow-capability-audit-write`. Container overrides have the form: ```yaml container.kubeaudit.io/[container name].[override identifier]: '' ``` Pod overrides have the form: ```yaml kubeaudit.io/[override identifier]: '' ``` Example of a resource with `AUDIT_WRITE` and `DAC_OVERRIDE` capabilities overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: container.kubeaudit.io/myContainer.allow-capability-audit-write: '' container.kubeaudit.io/myContainer.allow-capability-dac-override: '' spec: containers: - name: myContainer securityContext: capabilities: drop: - ALL add: - AUDIT_WRITE - DAC_OVERRIDE ``` Example of a resource with `AUDIT_WRITE` and `DAC_OVERRIDE` capabilities overridden for a whole pod: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: kubeaudit.io/allow-capability-audit-write: '' kubeaudit.io/allow-capability-dac-override: '' spec: containers: - name: myContainer securityContext: capabilities: drop: - ALL add: - AUDIT_WRITE - DAC_OVERRIDE ``` 07070100000106000081A400000000000000000000000166C635DC00000CEF000000000000000000000000000000000000003100000000kubeaudit-0.22.2/docs/auditors/deprecatedapis.md# Kubernetes Deprecated API Auditor (deprecatedapis) Finds any resource defined with a deprecated API version. ## General Usage ``` kubeaudit deprecatedapis [flags] ``` ### Flags | Short | Long | Description | Default | | :------ | :--------------------- | :-------------------------------------------- | :------------------ | | | --current-k8s-version | Kubernetes current version | | | | --targeted-k8s-version | Kubernetes version to migrate to | | Also see [Global Flags](/README.md#global-flags) ## Examples The `deprecatedapis` auditor finds the deprecated APIs in use, reports the versions where they will be removed, and recommends replacement APIs. ``` $ kubeaudit deprecatedapis -f "auditors/deprecatedapis/fixtures/cronjob.yml" ---------------- Results for --------------- apiVersion: batch/v1beta1 kind: CronJob metadata: name: hello -------------------------------------------- -- [warning] DeprecatedAPIUsed Message: batch/v1beta1 CronJob is deprecated in v1.21+, unavailable in v1.25+, introduced in v1.8+; use batch/v1 CronJob Metadata: DeprecatedMajor: 1 DeprecatedMinor: 21 IntroducedMajor: 1 IntroducedMinor: 8 RemovedMajor: 1 RemovedMinor: 25 ReplacementKind: CronJob ReplacementGroup: batch/v1 ``` The `deprecatedapis` auditor can be used with the `--current-k8s-version` flag. If the API is not yet deprecated for this version the auditor will produce an `info` otherwise a `warning`. ``` $ kubeaudit deprecatedapis --current-k8s-version 1.20 -f "auditors/deprecatedapis/fixtures/cronjob.yml" ---------------- Results for --------------- apiVersion: batch/v1beta1 kind: CronJob metadata: name: hello -------------------------------------------- -- [info] DeprecatedAPIUsed Message: batch/v1beta1 CronJob is deprecated in v1.21+, unavailable in v1.25+, introduced in v1.8+; use batch/v1 CronJob Metadata: DeprecatedMajor: 1 DeprecatedMinor: 21 IntroducedMajor: 1 IntroducedMinor: 8 RemovedMajor: 1 RemovedMinor: 25 ReplacementKind: CronJob ReplacementGroup: batch/v1 ``` The `deprecatedapis` auditor can be used with the `--targeted-k8s-version` flag. If the API is not available for the targeted version the auditor will produce an `error` otherwise a `warning` or `info` if the API is not yet deprecated for this version. ``` $ kubeaudit deprecatedapis --current-k8s-version 1.20 --targeted-k8s-version 1.25 -f "auditors/deprecatedapis/fixtures/cronjob.yml" ---------------- Results for --------------- apiVersion: batch/v1beta1 kind: CronJob metadata: name: hello -------------------------------------------- -- [error] DeprecatedAPIUsed Message: batch/v1beta1 CronJob is deprecated in v1.21+, unavailable in v1.25+, introduced in v1.8+; use batch/v1 CronJob Metadata: DeprecatedMajor: 1 DeprecatedMinor: 21 IntroducedMajor: 1 IntroducedMinor: 8 RemovedMajor: 1 RemovedMinor: 25 ReplacementKind: CronJob ReplacementGroup: batch/v1 ``` ## Override Errors Overrides are not currently supported for `deprecatedapis`. 07070100000107000081A400000000000000000000000166C635DC00000C71000000000000000000000000000000000000002900000000kubeaudit-0.22.2/docs/auditors/hostns.md# Host Namespaces Auditor (hostns) Finds containers that have HostPID, HostIPC or HostNetwork enabled. ## General Usage ``` kubeaudit hostns [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit hostns -f "auditors/hostns/fixtures/namespaces-all-true.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod namespace: namespaces-all-true -------------------------------------------- -- [error] NamespaceHostNetworkTrue Message: hostNetwork is set to 'true' in PodSpec. It should be set to 'false'. -- [error] NamespaceHostIPCTrue Message: hostIPC is set to 'true' in PodSpec. It should be set to 'false'. -- [error] NamespaceHostPIDTrue Message: hostPID is set to 'true' in PodSpec. It should be set to 'false'. ``` ## Explanation **HostPID** - Controls whether the pod containers can share the host process ID namespace. Note that when paired with ptrace this can be used to escalate privileges outside of the container (ptrace is forbidden by default). **HostIPC** - Controls whether the pod containers can share the host IPC namespace. **HostNetwork** - Controls whether the pod may use the node network namespace. Doing so gives the pod access to the loopback device, services listening on localhost, and could be used to snoop on network activity of other pods on the same node. All host namespaces should be disabled unless they are needed. They default to `false` so removing them is sufficient to pass the `hostns` audit, though they can also be explicitly set to `false` if desired. Example of a resource which **fails** the `hostns` audit: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: myContainer ``` For more information on host namespaces, see https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). Each host namespace field can be individually overridden using their respective override identifiers: | Host Namespace | Override Identifier | | :------------- | :--------------------- | | HostPID | `allow-namespace-host-PID` | | HostIPC | `allow-namespace-host-IPC` | | HostNetwork | `allow-namespace-host-network` | Container overrides have the form: ```yaml container.kubeaudit.io/[container name].[override identifier]: "" ``` Pod overrides have the form: ```yaml kubeaudit.io/[override identifier]: "" ``` Example of a resource with `HostPID` overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: container.kubeaudit.io/myContainer.allow-namespace-host-PID: "" spec: hostPID: true containers: - name: myContainer ``` Example of a resource with `HostPID` overridden for a whole pod: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: kubeaudit.io/allow-namespace-host-PID: "" spec: hostPID: true containers: - name: myContainer ``` 07070100000108000081A400000000000000000000000166C635DC000008F4000000000000000000000000000000000000002800000000kubeaudit-0.22.2/docs/auditors/image.md# Image Auditor (image) Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag. ## General Usage ``` kubeaudit image [flags] ``` ### Flags | Short | Long | Description | Default | | :------ | :-------- | :-------------------------------------------------------- | :------------------------------- | | -i | --image | Image and tag to check against. | | Also see [Global Flags](/README.md#global-flags) ## Examples The image and tag to look for are specified using the `-i/--image image:tag` flag. For example, `-i gcr.io/google_containers/echoserver:1.7` will look for containers using the `gcr.io/google_containers/echoserver` image which have a tag other than `1.7`. ``` $ kubeaudit image -i "scratch:1.6" -f "auditors/image/fixtures/image-tag-present.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment -------------------------------------------- -- [error] ImageTagIncorrect Message: Container tag is incorrect. It should be set to '1.6'. Metadata: Container: deployment ``` If the container image matches the provided image but the container image has no tag, a warning is produced: ``` $ kubeaudit image -i "scratch:1.6" -f "auditors/image/fixtures/image-tag-missing.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment -------------------------------------------- -- [warning] ImageTagMissing Message: Image tag is missing. Metadata: Container: container ``` The `image` auditor can be used to find all containers that use an image without a tag by omitting the `-i/--image` flag: ``` $ kubeaudit image -f "auditors/image/fixtures/image-tag-missing.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment -------------------------------------------- -- [warning] ImageTagMissing Message: Image tag is missing. Metadata: Container: container ``` ## Override Errors Overrides are not currently supported for `image`. 07070100000109000081A400000000000000000000000166C635DC00000C18000000000000000000000000000000000000002900000000kubeaudit-0.22.2/docs/auditors/limits.md# Limits Auditor (limits) Finds containers which exceed the specified CPU and memory limits or do not specify any. ## General Usage ``` kubeaudit limits [flags] ``` ### Flags | Short | Long | Description | Default | | :------ | :-------- | :-------------------------------------------------------- | :------------------------------- | | | --cpu | Max CPU limit | | | | --memory | Max memory limit | | Also see [Global Flags](/README.md#global-flags) ## Examples The max CPU is specified using the `--cpu` flag: ``` $ kubeaudit limits --cpu 600m -f "auditors/limits/fixtures/resources-limit.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod -------------------------------------------- -- [warning] LimitsCPUExceeded Message: CPU limit exceeded. It is set to '750m' which exceeds the max CPU limit of '600m'. Metadata: Container: container ContainerCpuLimit: 750m MaxCPU: 600m ``` The max memory is specified using the `--memory` flag: ``` $ kubeaudit limits --memory 384 -f "auditors/limits/fixtures/resources-limit.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod -------------------------------------------- -- [warning] LimitsMemoryExceeded Message: Memory limit exceeded. It is set to '512Mi' which exceeds the max Memory limit of '384'. Metadata: MaxMemory: 384 Container: container ContainerMemoryLimit: 512Mi ``` The CPU and memory can be audited at the same time by including both the `--cpu` and `--memory` flags: ``` $ kubeaudit limits --cpu 600m --memory 384 -f "auditors/limits/fixtures/resources-limit.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod -------------------------------------------- -- [warning] LimitsCPUExceeded Message: CPU limit exceeded. It is set to '750m' which exceeds the max CPU limit of '600m'. Metadata: Container: container ContainerCpuLimit: 750m MaxCPU: 600m -- [warning] LimitsMemoryExceeded Message: Memory limit exceeded. It is set to '512Mi' which exceeds the max Memory limit of '384'. Metadata: Container: container ContainerMemoryLimit: 512Mi MaxMemory: 384 ``` The `limits` auditor can be used to find all containers which do not specify a max CPU or memory by omitting the `--cpu` and `--memory` flags: ``` $ kubeaudit limits -f "auditors/limits/fixtures/resources-limit-nil.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod -------------------------------------------- -- [warning] LimitsNotSet Message: Resource limits not set. Metadata: Container: container ``` ## Override Errors Overrides are not currently supported for `limits`. 0707010000010A000081A400000000000000000000000166C635DC0000221B000000000000000000000000000000000000002900000000kubeaudit-0.22.2/docs/auditors/mounts.md# Sensitive Host Path Mounted Auditor (mounts) Finds containers that have sensitive host paths mounted. ## General Usage ``` kubeaudit mounts [flags] ``` ### Flags | Short | Long | Description | Default | | :------ | :---------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | | -d | --denyPathsList | List of sensitive paths that shouldn't be mounted. | [default sensitive host paths list](#Default-sensitive-host-paths-list) | Also see [Global Flags](/README.md#global-flags) #### Default sensitive host paths list | Host path | Description | | :------------------------------ | :---------------------------------------------------------------------- | | /proc | Pseudo-filesystem which provides an interface to kernel data structures | | / | Filesystem's root | | /etc | Directory that usually contains all system related configurations files | | /root | Home directory of the `root` user | | /var/run/docker.sock | Unix socket used to communicate with Docker daemon | | /var/run/crio/crio.sock | Unix socket used to communicate with the CRI-O Container Engine | | /run/containerd/containerd.sock | Unix socket used to communicate with the Containerd container runtime | | /home/admin | Home directory of the `admin` user | | /var/lib/kubelet | Directory for Kublet-related configuration | | /var/lib/kubelet/pki | Directory containing the certificate and private key of the kublet | | /etc/kubernetes | Directory containing Kubernetes related configuration | | /etc/kubernetes/manifests | Directory containing manifest of Kubernetes components | ## Examples ``` $ kubeaudit mounts -f auditors/mounts/fixtures/proc-mounted.yml ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod namespace: proc-mounted -------------------------------------------- -- [error] SensitivePathsMounted Message: Sensitive path mounted as volume: proc-volume (/proc -> /host/proc, readOnly: false). It should be removed from the container's mounts list. Metadata: Container: container MountName: proc-volume MountPath: /host/proc MountReadOnly: false MountVolume: proc-volume MountVolumeHostPath: /proc ``` ### Example with Config File If you don't want kubeaudit to raise errors for all the paths in the default list (`DefaultSensitivePaths`), you can provide a custom paths list in the config file. See [docs](docs/all.md) for more information. That way kubeaudit will only raise errors for those specific paths listed in the config file. `config.yaml` ```yaml --- enabledAuditors: mounts: true auditors: mounts: denyPathsList: ["/etc", "/var/run/docker.sock"] ``` `manifest.yaml` ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: example-namespace spec: template: spec: containers: - name: container image: scratch volumeMounts: - mountPath: /host/etc name: etc-volume - mountPath: /var/run/docker.sock name: docker-socket-volume volumes: - name: etc-volume hostPath: path: /etc - name: docker-socket-volume hostPath: path: /var/run/docker.sock ``` ```shell $ kubeaudit all --kconfig "config.yaml" -f "manifest.yaml" ---------------- Results for --------------- apiVersion: apps/v1beta2 kind: Deployment metadata: name: deployment namespace: example-namespace -------------------------------------------- -- [error] SensitivePathsMounted Message: Sensitive path mounted as volume: etc-volume (hostPath: /etc). It should be removed from the container's mounts list. Metadata: Container: container MountName: etc-volume MountPath: /host/etc MountReadOnly: false MountVolume: etc-volume MountVolumeHostPath: /etc -- [error] SensitivePathsMounted Message: Sensitive path mounted as volume: docker-socket-volume (hostPath: /var/run/docker.sock). It should be removed from the container's mounts list. Metadata: MountReadOnly: false MountVolume: docker-socket-volume MountVolumeHostPath: /var/run/docker.sock Container: container MountName: docker-socket-volume MountPath: /var/run/docker.sock ``` ### Example with Custom Paths List A custom paths list can be provided as a comma separated value list of paths using the `--denyPathsList` flag. These are the host paths you'd like to have kubeaudit raise an error when they are mounted in a container. `manifest.yaml` (example manifest) ```yaml volumes: - name: etc-volume hostPath: path: /etc - name: docker-socket-volume hostPath: path: /var/run/docker.sock ``` ```shell $ kubeaudit mounts --denyPathsList "/etc,/var/run/docker.sock" -f "manifest.yaml" ---------------- Results for --------------- apiVersion: apps/v1beta2 kind: Deployment metadata: name: deployment namespace: example-namespace -------------------------------------------- -- [error] SensitivePathsMounted Message: Sensitive path mounted as volume: etc-volume (hostPath: /etc). It should be removed from the container's mounts list. Metadata: Container: container MountName: etc-volume MountPath: /host/etc MountReadOnly: false MountVolume: etc-volume MountVolumeHostPath: /etc -- [error] SensitivePathsMounted Message: Sensitive path mounted as volume: docker-socket-volume (hostPath: /var/run/docker.sock). It should be removed from the container's mounts list. Metadata: Container: container MountName: docker-socket-volume MountPath: /var/run/docker.sock MountReadOnly: false MountVolume: docker-socket-volume MountVolumeHostPath: /var/run/docker.sock ``` ## Explanation Mounting some sensitive host paths (like `/etc`, `/proc`, or `/var/run/docker.sock`) may allow a container to access sensitive information from the host like credentials or to spy on other workloads' activity. These sensitive paths should not be mounted. Example of a resource which **fails** the `mounts` audit: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: container image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume volumes: - name: proc-volume hostPath: path: /proc ``` ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). The override identifier has the format `allow-host-path-mount-[mount name]` which allows for each mount to be individually overridden. Example of resource with `mounts` overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: #PodTemplateSpec metadata: labels: container.kubeaudit.io/container2.allow-host-path-mount-proc-volume: "SomeReason" spec: #PodSpec containers: - name: container1 image: scratch - name: container2 image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume volumes: - name: proc-volume hostPath: path: /proc ``` Example of resource with `mounts` overridden for a whole pod: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: #PodTemplateSpec metadata: labels: kubeaudit.io/allow-host-path-mount-proc-volume: "SomeReason" spec: #PodSpec containers: - name: container1 image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume - name: container2 image: scratch volumeMounts: - mountPath: /host/proc name: proc-volume volumes: - name: proc-volume hostPath: path: /proc ``` 0707010000010B000081A400000000000000000000000166C635DC00000FD7000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/docs/auditors/netpols.md# Default Deny NetworkPolicies for Namespaces Auditor (netpols) Finds namespaces that do not have a default-deny network policy. ## General Usage ``` kubeaudit netpols [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit netpols -f "auditors/netpols/fixtures/namespace-missing-default-deny-netpol.yml" ---------------- Results for --------------- apiVersion: v1 kind: Namespace metadata: name: namespace-missing-default-deny-netpol -------------------------------------------- -- [error] MissingDefaultDenyIngressAndEgressNetworkPolicy Message: Namespace is missing a default deny ingress and egress NetworkPolicy. Metadata: Namespace: namespace-missing-default-deny-netpol ``` ## Explanation Just like with firewall rules, the best practice is to deny all internet traffic by default and explicitly allow expected traffic (that is, allow expected traffic rather than deny unexpected traffic). This can be done by creating a Network Policy for each namespace which denies all ingress (incoming) and egress (outgoing) traffic. This Network Policy should have an empty pod selector: ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: default spec: podSelector: {} policyTypes: - Ingress - Egress ``` To allow traffic to a pod, an additional Network Policy can be created which selects that pod. For more information on network policies, see https://kubernetes.io/docs/concepts/services-networking/network-policies/ ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). The `netpols` auditor uses a unique override label type not used by any other auditor because the label applies to a namespace (rather than a container or pod): ``` kubeaudit.io/[override identifier]: "" ``` Deny-all ingress and egress network policies can be individually overridden using their respective override identifiers: | Traffic Type | Override Identifier | | :------------- | :----------------------------------------------- | | Ingress | `allow-non-default-deny-ingress-network-policy` | | Egress | `allow-non-default-deny-egress-network-policy` | The override label is placed directly on the Namespace resource: ```yaml apiVersion: v1 kind: Namespace metadata: name: "default" labels: kubeaudit.io/allow-non-default-deny-ingress-network-policy: "" ``` ### Override Example Consider this Network Policy which denies all egress traffic in the `my-namespace` namespace, but allows all ingress traffic: ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: my-namespace spec: podSelector: {} policyTypes: - Egress --- apiVersion: v1 kind: Namespace metadata: name: my-namespace ``` The `netpols` auditor will produce an error because there is no `deny-all` Network Policy for ingress traffic: ``` ---------------- Results for --------------- apiVersion: v1 kind: Namespace metadata: name: my-namespace -------------------------------------------- -- [error] MissingDefaultDenyIngressNetworkPolicy Message: All ingress traffic should be blocked by default for namespace my-namespace. Metadata: Namespace: my-namespace ``` This error can be overridden by adding the `kubeaudit.io/allow-non-default-deny-ingress-network-policy: ""` label to the corresponding Namespace resource: ```yaml apiVersion: v1 kind: Namespace metadata: name: "my-namespace" labels: kubeaudit.io/allow-non-default-deny-ingress-network-policy: "" ``` The auditor will now produce a warning instead of an error: ``` ---------------- Results for --------------- apiVersion: v1 kind: Namespace metadata: name: my-namespace -------------------------------------------- -- [warning] MissingDefaultDenyIngressNetworkPolicyAllowed Message: All ingress traffic should be blocked by default for namespace my-namespace. Metadata: Namespace: my-namespace ``` 0707010000010C000081A400000000000000000000000166C635DC00001313000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/docs/auditors/nonroot.md# runAsNonRoot Auditor (nonroot) Finds containers allowed to run as root. ## General Usage ``` kubeaudit nonroot [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit nonroot -f "auditors/nonroot/fixtures/run-as-non-root-nil.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: run-as-non-root-nil -------------------------------------------- -- [error] RunAsNonRootPSCNilCSCNil Message: runAsNonRoot is not set in container SecurityContext nor the PodSecurityContext. It should be set to 'true' in at least one of the two. Metadata: Container: container ``` ## Explanation Containers should be run as a non-root user with the minimum required permissions (principle of least privilege). This can be done by setting `runAsNonRoot` to `true` in either the PodSecurityContext or container SecurityContext. If `runAsNonRoot` is unset in the Container SecurityContext, it will inherit the value of the Pod SecurityContext. If `runAsNonRoot` is unset in the Pod SecurityContext, it defaults to `false` which means it must be explicitly set to `true` in either the Container SecurityContext or the Pod SecurityContext for the `nonroot` audit to pass. Note that the Container SecurityContext takes precedence over the Pod SecurityContext so setting `runAsNonRoot` to `false` in the Container SecurityContext will always fail the `nonroot` audit unless an [override](#override-errors) is used. Ideally, `runAsNonRoot` should be set to `true` in the PodSecurityContext: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: #PodTemplateSpec spec: #PodSpec securityContext: #PodSecurityContext runAsNonRoot: true containers: - name: myContainer ``` Alternatively it's possible to enforce non-root containers by setting `runAsUser` to a non-root UID (>0) in either the PodSecurityContext or container SecurityContext. Conversely, if `runAsUser` is set to `0` in either the PodSecurityContext or container SecurityContext then the container will always run as root and so the audit will fail. If `runAsUser` is set to a non-root UID (either in PodSecurityContext or container SecurityContext) it won't matter if `runAsNonRoot` is set to `false` or `nil` and so the audit will always pass. As for `runAsNonRoot`, ideally, `runAsUser` should be set to a non-root UID in the PodSecurityContext: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: #PodTemplateSpec spec: #PodSpec securityContext: #PodSecurityContext runAsUser: 1000 containers: - name: myContainer ``` If a container needs to run as root, it should be enabled for that container only in the container's SecurityContext. This will require an override label so kubeaudit knows it is intentional. See [Override Errors](#override-errors). For more information on pod and container security contexts see https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). Override identifer: `allow-run-as-root` Container overrides have the form: ```yaml container.kubeaudit.io/[container name].allow-run-as-root: "" ``` Pod overrides have the form: ```yaml kubeaudit.io/allow-run-as-root: "" ``` Example of resource with `nonroot` overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: #PodTemplateSpec metadata: labels: container.kubeaudit.io/myContainer.allow-run-as-root: "" spec: #PodSpec securityContext: #PodSecurityContext runAsNonRoot: true containers: - name: myContainer securityContext: #SecurityContext runAsNonRoot: false - name: myContainer2 ``` Example of resource with `nonroot` overridden for a whole pod: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: #PodTemplateSpec metadata: labels: kubeaudit.io/allow-run-as-root: "" spec: #PodSpec securityContext: #PodSecurityContext runAsNonRoot: true containers: - name: myContainer securityContext: #SecurityContext runAsNonRoot: false - name: myContainer2 securityContext: #SecurityContext runAsNonRoot: false ``` Example of resource with `nonroot` overridden for a specific container using `runAsUser`: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: #PodTemplateSpec metadata: labels: container.kubeaudit.io/myContainer.allow-run-as-root: "" spec: #PodSpec securityContext: #PodSecurityContext runAsUser: 1000 containers: - name: myContainer securityContext: #SecurityContext runAsUser: 0 - name: myContainer2 ``` 0707010000010D000081A400000000000000000000000166C635DC00000976000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/docs/auditors/privesc.md# Privilege Escalation Allowed Auditor (privesc) Finds containers that allow privilege escalation. ## General Usage ``` kubeaudit privesc [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit privesc -f "auditors/privesc/fixtures/allow-privilege-escalation-nil.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: allow-privilege-escalation-nil -------------------------------------------- -- [error] AllowPrivilegeEscalationNil Message: allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'. Metadata: Container: container ``` ## Explanation `allowPrivilegeEscalation` controls whether a process can gain more privileges than its parent process. Privilege escalation is disabled by setting `allowPrivilegeEscalation` to `false` in the container SecurityContext. The field defaults to `true` so it must be explicitly set to `false` to pass the `privesc` audit: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer securityContext: allowPrivilegeEscalation: false ``` For more information on pod and container security contexts see https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). Override identifier: `allow-privilege-escalation` Container overrides have the form: ```yaml container.kubeaudit.io/[container name].allow-privilege-escalation: "" ``` Pod overrides have the form: ```yaml kubeaudit.io/allow-privilege-escalation: "" ``` Example of resource with `privesc` overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: container.kubeaudit.io/myContainer.allow-privilege-escalation: "" spec: containers: - name: myContainer securityContext: allowPrivilegeEscalation: true ``` Example of resource with `privesc` overridden for a whole pod: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: kubeaudit.io/allow-privilege-escalation: "" spec: containers: - name: myContainer securityContext: allowPrivilegeEscalation: true ``` 0707010000010E000081A400000000000000000000000166C635DC000009E6000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/docs/auditors/privileged.md# Privileged Auditor (privileged) Finds containers running as privileged. ## General Usage ``` kubeaudit privileged [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit privileged -f "auditors/privileged/fixtures/privileged-true.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset namespace: privileged-true -------------------------------------------- -- [error] PrivilegedTrue Message: privileged is set to 'true' in container SecurityContext. It should be set to 'false'. Metadata: Container: container ``` ## Explanation Running a container as privileged gives all capabilities to the container, and it also lifts all the limitations enforced by the device cgroup controller. In other words, the container can then do almost everything that the host can do. This option exists to allow special use-cases, like running Docker within Docker, but should not be used in most cases. To prevent a container from running as privileged, `privileged` should be set to `false` in the container SecurityContext. The field defaults to `false` so omitting the field is sufficient to pass the `privileged` audit: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer securityContext: privileged: false ``` For more information on pod and container security contexts see https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). Override identifier: `allow-privileged` Container overrides have the form: ```yaml container.kubeaudit.io/[container name].allow-privileged: "" ``` Pod overrides have the form: ```yaml kubeaudit.io/allow-privileged: "" ``` Example of resource with `privileged` overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: container.kubeaudit.io/myContainer.allow-privilege-escalation: "" spec: containers: - name: myContainer securityContext: privileged: true ``` Example of resource with `privileged` overridden for a whole pod: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: kubeaudit.io/allow-privileged: "" spec: containers: - name: myContainer securityContext: privileged: true ``` 0707010000010F000081A400000000000000000000000166C635DC00000A35000000000000000000000000000000000000002900000000kubeaudit-0.22.2/docs/auditors/rootfs.md# readOnlyRootFilesystem Auditor (rootfs) Finds containers which do not have a read-only filesystem. ## General Usage ``` kubeaudit rootfs [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit rootfs -f "auditors/rootfs/fixtures/read-only-root-filesystem-nil.yml" ---------------- Results for --------------- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: read-only-root-filesystem-nil -------------------------------------------- -- [error] ReadOnlyRootFilesystemNil Message: readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'. Metadata: Container: container ``` ## Explanation If a container does not need to write files, it should be run with a read-only filesystem. To run a container with a read-only filesystem, `readOnlyRootFilesystem` should be set to `true` in the container SecurityContext. The field defaults to `false` so it must be explicitly set to `true` to pass the `rootfs` audit: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer securityContext: readOnlyRootFilesystem: true ``` If a container needs to write files, an override label needs to be used so kubeaudit knows it is intentional. See [Override Errors](#override-errors). For more information on pod and container security contexts see https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ ## Override Errors First, see the [Introduction to Override Errors](/README.md#override-errors). Override identifier: `allow-read-only-root-filesystem-false` Container overrides have the form: ```yaml container.kubeaudit.io/[container name].allow-read-only-root-filesystem-false: "" ``` Pod overrides have the form: ```yaml kubeaudit.io/allow-read-only-root-filesystem-false: "" ``` Example of resource with `rootfs` overridden for a specific container: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: container.kubeaudit.io/myContainer.allow-read-only-root-filesystem-false: "" spec: containers: - name: myContainer securityContext: readOnlyRootFilesystem: false ``` Example of resource with `rootfs` overridden for a whole pod: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: metadata: labels: kubeaudit.io/allow-read-only-root-filesystem-false: "" spec: containers: - name: myContainer securityContext: readOnlyRootFilesystem: false ``` 07070100000110000081A400000000000000000000000166C635DC00000846000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/docs/auditors/seccomp.md# Seccomp Auditor (seccomp) Finds containers running without Seccomp. ## General Usage ``` kubeaudit seccomp [flags] ``` See [Global Flags](/README.md#global-flags) ## Examples ``` $ kubeaudit seccomp -f "auditors/seccomp/fixtures/seccomp-profile-missing.yml" ---------------- Results for --------------- apiVersion: v1 kind: Pod metadata: name: pod namespace: seccomp-profile-missing -------------------------------------------- -- [error] SeccompProfileMissing Message: Pod Seccomp profile is missing. Seccomp profile should be added to the pod SecurityContext. ``` ## Explanation Seccomp (Secure computing mode) is a Linux kernel feature. Seccomp is enabled by adding a seccomp profile to the security context. The seccomp profile can be either added to a pod security context, which enables seccomp for all containers within that pod, or a security context, which enables seccomp only for that container. The seccomp profile added to a pod security context has the following format: ``` spec: securityContext: seccompProfile: type: [seccomp profile] ``` The seccomp profile added to a container security context has the following format: ``` spec: containers: - name: [container name] image: [container image] securityContext: seccompProfile: type: [seccomp profile] ``` Ideally, the pod security context should be used. The value of the seccomp profile type can be set to either the default profile (`RuntimeDefault`) or a custom profile (`Localhost`). For `Localhost` type `localhostProfile: [profile file]` should be added. Example of a resource which passes the `seccomp` audit: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: myContainer ``` To learn more about Seccomp, see https://en.wikipedia.org/wiki/Seccomp To learn more about Seccomp in Kubernetes, see https://kubernetes.io/docs/tutorials/security/seccomp/ ## Override Errors Overrides are not currently supported for `seccomp`. 07070100000111000081A400000000000000000000000166C635DC000013CC000000000000000000000000000000000000002100000000kubeaudit-0.22.2/docs/autofix.md# Autofix (autofix) Automatically fixes security issues. **Note**: `autofix` can only be used in manifest mode. ## General Usage ``` kubeaudit autofix -f [manifest] [flags] ``` ## Flags | Short | Long | Description | Default | | :------ | :--------- | :---------------------------------------- | :--------------------------------------- | | -o | --outfile | File to write fixed manifest to | | | -k | --kconfig | Path to kubeaudit config file | | Also see [Global Flags](/README.md#global-flags) ## Examples Consider this simple manifest file `manifest.yml`: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer ``` The `autofix` command will make the manifest secure!: ``` kubeaudit autofix -f "manifest.yml" ``` Fixed manifest: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer resources: {} securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true runAsNonRoot: true automountServiceAccountToken: false securityContext: seccompProfile: type: RuntimeDefault metadata: annotations: container.apparmor.security.beta.kubernetes.io/myContainer: runtime/default selector: null strategy: {} metadata: ``` ### Example with Multiple Resources The `autofix` command works on manifest files containing multiple resources: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer --- apiVersion: v1 kind: Pod spec: containers: - name: myContainer2 image: polinux/stress ``` Fixed manifest: ```yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myContainer resources: {} securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true runAsNonRoot: true automountServiceAccountToken: false securityContext: seccompProfile: type: RuntimeDefault metadata: annotations: container.apparmor.security.beta.kubernetes.io/myContainer: runtime/default selector: null strategy: {} metadata: --- apiVersion: v1 kind: Pod spec: containers: - name: myContainer2 image: polinux/stress resources: {} securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true runAsNonRoot: true automountServiceAccountToken: false securityContext: seccompProfile: type: RuntimeDefault metadata: annotations: container.apparmor.security.beta.kubernetes.io/myContainer2: runtime/default ``` ### Example with Comments The `autofix` command supports comments! ```yaml # This is a sample Kubernetes config file # # Autofix supports comments! %YAML 1.1 %TAG ! !foo %TAG !yaml! tag:yaml.org,2002: --- apiVersion: apps/v1 kind: Deployment # PodSpec spec: # PodTemplate template: # ContainerSpec spec: containers: - name: myContainer # this is a sample container ``` Fixed manifest: ```yaml # This is a sample Kubernetes config file # # Autofix supports comments! %YAML 1.1 %TAG ! !foo %TAG !yaml! tag:yaml.org,2002: --- apiVersion: apps/v1 kind: Deployment # PodSpec spec: # PodTemplate template: # ContainerSpec spec: containers: - name: myContainer # this is a sample container resources: {} securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL privileged: false readOnlyRootFilesystem: true runAsNonRoot: true automountServiceAccountToken: false securityContext: seccompProfile: type: RuntimeDefault metadata: annotations: container.apparmor.security.beta.kubernetes.io/myContainer: runtime/default selector: null strategy: {} metadata: ``` ### Example with Custom Output File To write the fixed manifest to a different file, use the `--outfile/-o` flag: ``` kubeaudit autofix -f "manifest.yml" -o "fixed.yaml" ``` ### Using Custom Rules with Kubeaudit Config File To fix a manifest based on custom rules specified on a kubeaudit config file (e.g disable some auditors), use the `-k/--kconfig` flag. ``` kubeaudit autofix -k "/path/to/kubeaudit-config.yml" -f "/path/to/manifest.yml" -o "/path/to/fixed" ``` Also see [Configuration File](/README.md#configuration-file) 07070100000112000081A400000000000000000000000166C635DC00001832000000000000000000000000000000000000002100000000kubeaudit-0.22.2/docs/cluster.md# Running kubeaudit in a Cluster Kubeaudit can be run in a Kubernetes cluster by using a Docker image. We no longer release images to Docker Hub (since Docker Hub sunset Free Team organizations). For the time being, [old images](https://hub.docker.com/r/shopify/kubeaudit) are still available but may stop being available at any time. We will start publishing images to the Github Container registry soon. ## Without RBAC Example Job configuration: ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: kubeaudit namespace: default --- apiVersion: batch/v1 kind: Job metadata: name: kubeaudit namespace: default spec: template: metadata: annotations: container.apparmor.security.beta.kubernetes.io/kubeaudit: runtime/default spec: serviceAccountName: kubeaudit restartPolicy: OnFailure securityContext: seccompProfile: type: RuntimeDefault containers: - name: kubeaudit image: shopify/kubeaudit:v0.11 args: ["all", "--exitcode", "0"] securityContext: allowPrivilegeEscalation: false capabilities: drop: ["all"] privileged: false readOnlyRootFilesystem: true runAsNonRoot: true ``` ## With RBAC If RBAC is enabled on the cluster: ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: kubeaudit namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: kubeaudit rules: - apiGroups: [""] resources: - pods - podtemplates - replicationcontrollers - namespaces - serviceaccounts verbs: ["list"] - apiGroups: ["apps"] resources: - daemonsets - statefulsets - deployments verbs: ["list"] - apiGroups: ["batch"] resources: - cronjobs verbs: ["list"] - apiGroups: ["networking.k8s.io"] resources: - networkpolicies verbs: ["list"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: kubeaudit subjects: - kind: ServiceAccount name: kubeaudit namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubeaudit --- apiVersion: batch/v1 kind: Job metadata: name: kubeaudit namespace: default spec: template: metadata: annotations: container.apparmor.security.beta.kubernetes.io/kubeaudit: runtime/default spec: serviceAccountName: kubeaudit restartPolicy: OnFailure securityContext: seccompProfile: type: RuntimeDefault containers: - name: kubeaudit image: shopify/kubeaudit:v0.11 args: ["all", "--exitcode", "0"] securityContext: allowPrivilegeEscalation: false capabilities: drop: ["all"] privileged: false readOnlyRootFilesystem: true runAsNonRoot: true ``` ## With RBAC and a Specific Namespace If you are running kubeaudit on a specific namespace and don't want to grant it cluster wide access, the binding can be made into a namespaced binding, but note that kubeaudit will still need to be able to list namespaces at the cluster level (as namespace resources don't have a namespaced scope). In the following example, the `kubeaudit` Job is created in the `kubeaudit` namespace and is assigned a ServiceAccount which can list namespaces at a cluster scope but can only list the other resources for the provided namespace. **Important**: Replace the two instances of `<TARGET_NAMESPACE>` with the namespace you want kubeaudit to audit: ```yaml # Optionally, run kubeaudit in its own namespace apiVersion: v1 kind: Namespace metadata: name: kubeaudit --- # Don't allow internet traffic in or out of the kubeaudit namespace apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: kubeaudit spec: policyTypes: - Ingress - Egress --- apiVersion: v1 kind: ServiceAccount metadata: name: kubeaudit namespace: kubeaudit --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: kubeaudit-namespaces rules: - apiGroups: [""] resources: - namespaces verbs: ["list"] --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: kubeaudit rules: - apiGroups: [""] resources: - pods - podtemplates - replicationcontrollers - serviceaccounts verbs: ["list"] - apiGroups: ["apps"] resources: - daemonsets - statefulsets - deployments verbs: ["list"] - apiGroups: ["batch"] resources: - cronjobs verbs: ["list"] - apiGroups: ["networking.k8s.io"] resources: - networkpolicies verbs: ["list"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: kubeaudit-namespaces subjects: - kind: ServiceAccount name: kubeaudit namespace: kubeaudit roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubeaudit-namespaces --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: kubeaudit namespace: <TARGET_NAMESPACE> subjects: - kind: ServiceAccount name: kubeaudit namespace: kubeaudit roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubeaudit --- apiVersion: batch/v1 kind: Job metadata: name: kubeaudit namespace: kubeaudit spec: template: metadata: annotations: container.apparmor.security.beta.kubernetes.io/kubeaudit: runtime/default spec: serviceAccountName: kubeaudit restartPolicy: OnFailure securityContext: seccompProfile: type: RuntimeDefault containers: - name: kubeaudit image: shopify/kubeaudit:v0.11 args: ["all", "--exitcode", "0", "--namespace", "<TARGET_NAMESPACE>"] securityContext: allowPrivilegeEscalation: false capabilities: drop: ["all"] privileged: false readOnlyRootFilesystem: true runAsNonRoot: true ``` 07070100000113000081A400000000000000000000000166C635DC000008EA000000000000000000000000000000000000002100000000kubeaudit-0.22.2/docs/release.md# How to Create a Kubeaudit Release 1. Make sure you're on main and have the latest changes. 2. Find the [latest release](https://github.com/Shopify/kubeaudit/releases). > We use [semver](https://semver.org/) versioning. In semver, version numbers have the format `v<MAJOR>.<MINOR>.<PATCH>`. However, because we still consider Kubeaudit to be in "alpha", the major number is always 0. This means that we do not maintain versions from before a breaking change, and updating to a new minor version may introduce a breaking change. If the changes since the most recent release are bug fixes only, bump the last number (the patch version). If any of the changes since the last release include a new feature or breaking change, bump the second number (the minor version) and set the last number to 0 (the patch version). For example, if the latest release is `v0.11.5` and there were only bug fixes merged to main since then, the next version number will be `v0.11.6`. If there were new features added or a breaking change was made, the next version would be `v0.12.0`. 3. Update the `VERSION` file if necessary. You'll have to open / merge a PR to do this. 4. Create a tag with the new version and push it up to Github: ``` git tag -a <VERSION> -m "<VERSION>" git push origin <VERSION> ``` For example: ``` git tag -a v0.11.6 -m "v0.11.6" git push origin v0.11.6 ``` 5. Once you push the tag, the release Github action will be triggered and generate a draft release in Github, allowing you to double check it and make changes to the Changelog. Find the [draft release](https://github.com/Shopify/kubeaudit/releases) and make sure there are no commits to main since the release. > If there are commits to main since the release, this may mean you didn't make the tag on main or your main is out of date. 6. Click `Edit` on the right of the draft release and tidy up the Changelog if necessary. We like to add thank you's to external contributors, for example: ``` 202e355 Fixed code quality issues using DeepSource (#315) - Thank you @withshubh for the contribution! ``` Optionally, you can click on "Generate release notes", which adds Markdown for all the merged pull requests from the diff and contributors of the release. 7. Click on `Publish release` at the bottom. 07070100000114000081A400000000000000000000000166C635DC00000B5D000000000000000000000000000000000000002800000000kubeaudit-0.22.2/example_custom_test.gopackage kubeaudit_test import ( "fmt" "strings" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" log "github.com/sirupsen/logrus" ) func NewCustomAuditor() kubeaudit.Auditable { return &myAuditor{} } // Your auditor must implement the Auditable interface, which requires only one method: Audit(). type myAuditor struct{} // The Audit function takes in a resource to audit and returns audit results for that resource. // // Params // resource: Read-only. The resource to audit. // resources: Read-only. A reference to all resources. Can be used for context though most auditors don't need this. // // Return // auditResults: The results for the audit. Each result can optionally include a PendingFix object to // define autofix behaviour (see below). func (a *myAuditor) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { return []*kubeaudit.AuditResult{ { Auditor: "Awesome", Rule: "MyAudit", Severity: kubeaudit.Error, Message: "My custom error", PendingFix: &myAuditorFix{ newVal: "bye", }, }, }, nil } // To provide autofix behaviour for an audit result, implement the PendingFix interface. The PendingFix interface // has two methods: Plan() and Apply(). type myAuditorFix struct { newVal string } // The Plan method explains what fix will be applied by Apply(). // // Return // plan: A human-friendly explanation of what Apply() will do func (f *myAuditorFix) Plan() string { return fmt.Sprintf("Set label 'hi' to '%s'", f.newVal) } // The Apply method applies a fix to a resource. // // Params // resource: A reference to the resource that should be fixed. // // Return // newResources: New resources created as part of the fix. Generally, it should not be necessary to create // new resources, only modify the passed in resource. func (f *myAuditorFix) Apply(resource k8s.Resource) []k8s.Resource { setLabel(resource, "hi", f.newVal) return nil } // This is just a helper function func setLabel(resource k8s.Resource, key, value string) { switch kubeType := resource.(type) { case *k8s.PodV1: kubeType.Labels[key] = value case *k8s.DeploymentV1: kubeType.Labels[key] = value } } // A sample Kubernetes manifest file var manifest = ` apiVersion: apps/v1 kind: Deployment metadata: name: myAuditor spec: template: spec: containers: - name: myContainer ` // ExampleCustomAuditor shows how to use a custom auditor func Example_customAuditor() { // Initialize kubeaudit with your custom auditor auditor, err := kubeaudit.New([]kubeaudit.Auditable{NewCustomAuditor()}) if err != nil { log.Fatal(err) } // Run the audit in the mode of your choosing. Here we use manifest mode. report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } // Print the results to screen report.PrintResults() } 07070100000115000081A400000000000000000000000166C635DC000015C9000000000000000000000000000000000000002100000000kubeaudit-0.22.2/example_test.gopackage kubeaudit_test import ( "fmt" "os" "strings" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/image" "github.com/Shopify/kubeaudit/config" "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" ) // Example shows how to audit and fix a Kubernetes manifest file func Example() { // A sample Kubernetes manifest file manifest := ` apiVersion: apps/v1 kind: Deployment metadata: name: myAuditor spec: template: spec: containers: - name: myContainer ` // Initialize all the security auditors using default configuration allAuditors, err := all.Auditors(config.KubeauditConfig{}) if err != nil { log.Fatal(err) } // Initialize kubeaudit auditor, err := kubeaudit.New(allAuditors) if err != nil { log.Fatal(err) } // Run the audit in manifest mode report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } // Print the audit results to screen report.PrintResults() // Print the plan to screen. These are the steps that will be taken by calling "report.Fix()". fmt.Println("\nPlan:") report.PrintPlan(os.Stdout) // Print the fixed manifest to screen. Note that this leaves the original manifest unmodified. fmt.Println("\nFixed manifest:") err = report.Fix(os.Stdout) if err != nil { log.Fatal(err) } } // ExampleAuditLocal shows how to run kubeaudit in local mode func Example_auditLocal() { // Initialize all the security auditors using default configuration allAuditors, err := all.Auditors(config.KubeauditConfig{}) if err != nil { log.WithError(err).Fatal("Error initializing all auditors") } // Initialize kubeaudit auditor, err := kubeaudit.New(allAuditors) if err != nil { log.Fatal(err) } // Run the audit in local mode report, err := auditor.AuditLocal("", "", kubeaudit.AuditOptions{}) if err != nil { log.Fatal(err) } // Print the audit results to screen report.PrintResults() } // ExampleAuditCluster shows how to run kubeaudit in cluster mode (only works if kubeaudit is being run from a container insdie of a cluster) func Example_auditCluster() { // Initialize all the security auditors using default configuration allAuditors, err := all.Auditors(config.KubeauditConfig{}) if err != nil { log.Fatal(err) } // Initialize kubeaudit auditor, err := kubeaudit.New(allAuditors) if err != nil { log.Fatal(err) } // Run the audit in cluster mode. Note this will fail if kubeaudit is not running within a cluster. report, err := auditor.AuditCluster(kubeaudit.AuditOptions{}) if err != nil { log.Fatal(err) } // Print the audit results to screen report.PrintResults() } // ExampleAuditorSubset shows how to run kubeaudit with a subset of auditors func Example_auditorSubset() { // A sample Kubernetes manifest file manifest := ` apiVersion: apps/v1 kind: Deployment metadata: name: myAuditor spec: template: spec: containers: - name: myContainer ` // Initialize the auditors you want to use auditor, err := kubeaudit.New([]kubeaudit.Auditable{ apparmor.New(), image.New(image.Config{Image: "myimage:mytag"}), }) if err != nil { log.Fatal(err) } // Run the audit in the mode of your choosing. Here we use manifest mode. report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } // Print the audit results to screen report.PrintResults() } // ExampleConfig shows how to use a kubeaudit with a config file. // A kubeaudit config can be used to specify which security auditors to run, and to specify configuration // for those auditors. func Example_config() { configFile := "config/config.yaml" // A sample Kubernetes manifest file manifest := ` apiVersion: apps/v1 kind: Deployment metadata: name: myAuditor spec: template: spec: containers: - name: myContainer ` // Open the configuration file reader, err := os.Open(configFile) if err != nil { log.WithError(err).Fatal("Unable to open config file ", configFile) } // Load the config conf, err := config.New(reader) if err != nil { log.WithError(err).Fatal("Error parsing config file ", configFile) } // Initialize security auditors using the configuration auditors, err := all.Auditors(conf) if err != nil { log.Fatal(err) } // Initialize kubeaudit auditor, err := kubeaudit.New(auditors) if err != nil { log.Fatal(err) } // Run the audit in the mode of your choosing. Here we use manifest mode. report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } // Print the audit results to screen report.PrintResults() } // ExamplePrintOptions shows how to use different print options for printing audit results. func Example_printOptions() { auditor, err := kubeaudit.New([]kubeaudit.Auditable{apparmor.New()}) if err != nil { log.Fatal(err) } report, err := auditor.AuditLocal("", "", kubeaudit.AuditOptions{}) if err != nil { log.Fatal(err) } // Print the audit results to a file f, err := os.Create("output.txt") if err != nil { log.Fatal(err) } defer f.Close() defer os.Remove("output.txt") report.PrintResults(kubeaudit.WithWriter(f)) // Only print audit results with severity of Error (ignore info and warning) report.PrintResults(kubeaudit.WithMinSeverity(kubeaudit.Error)) // Print results as JSON report.PrintResults(kubeaudit.WithFormatter(&logrus.JSONFormatter{})) } 07070100000116000081A400000000000000000000000166C635DC00001105000000000000000000000000000000000000001800000000kubeaudit-0.22.2/fix.gopackage kubeaudit import ( "bytes" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/internal/yaml" "github.com/Shopify/kubeaudit/pkg/k8s" ) func fix(results []Result) ([]byte, error) { var outputBytes [][]byte var newResources []k8s.Resource if len(results) == 0 { return []byte{}, nil } // Fix all the resources for _, result := range results { for _, auditResult := range result.GetAuditResults() { newResources = append(newResources, auditResult.Fix(result.GetResource().Object())...) } } // Convert all the resources to bytes for _, result := range results { if result.GetResource().Object() == nil { outputBytes = append(outputBytes, result.GetResource().Bytes()) continue } fixedresourceBytes, err := resourceToBytes(result.GetResource().Object(), result.GetResource().Bytes()) if err != nil { return nil, err } outputBytes = append(outputBytes, fixedresourceBytes) } // Convert all the new resources to bytes for _, newResource := range newResources { fixedresourceBytes, err := resourceToBytes(newResource, nil) if err != nil { return nil, err } outputBytes = append(outputBytes, fixedresourceBytes) } fixedManifest := bytes.Join(outputBytes, []byte("---")) return fixedManifest, nil } func resourceToBytes(fixedResource k8s.Resource, origResourceBytes []byte) ([]byte, error) { fixedresourceBytes, err := k8sinternal.EncodeResource(fixedResource) if err != nil { return nil, err } if origResourceBytes == nil { // This is a new resource (not in the original manifest) // Add a leading newline fixedresourceBytes = append([]byte{'\n'}, fixedresourceBytes...) } else { fixedresourceBytes, err = yaml.Merge(origResourceBytes, fixedresourceBytes) if err != nil { return nil, err } // Add any leading and trailing whitespace that was present in the original fixedresourceBytes = bytes.Replace(origResourceBytes, bytes.TrimSpace(origResourceBytes), fixedresourceBytes, 1) // Remove the redundant trailing newline if fixedresourceBytes[len(fixedresourceBytes)-1] == '\n' { fixedresourceBytes = fixedresourceBytes[:len(fixedresourceBytes)-1] } } fixedresourceBytes, err = cleanupManifest(origResourceBytes, fixedresourceBytes) if err != nil { return nil, err } return fixedresourceBytes, nil } // TODO do this better?? func cleanupManifest(origData, finalData []byte) ([]byte, error) { objectMetacreationTs := []byte("\n creationTimestamp: null\n") specTemplatecreationTs := []byte("\n creationTimestamp: null\n") jobSpecTemplatecreationTs := []byte("\n creationTimestamp: null\n") nullStatus := []byte("\nstatus: {}\n") nullReplicaStatus := []byte("status:\n replicas: 0\n") nullLBStatus := []byte("status:\n loadBalancer: {}\n") nullMetaStatus := []byte("\n status: {}\n") var hasObjectMetacreationTs, hasSpecTemplatecreationTs, hasJobSpecTemplatecreationTs, hasNullStatus, hasNullReplicaStatus, hasNullLBStatus, hasNullMetaStatus bool if origData != nil { hasObjectMetacreationTs = bytes.Contains(origData, objectMetacreationTs) hasSpecTemplatecreationTs = bytes.Contains(origData, specTemplatecreationTs) hasJobSpecTemplatecreationTs = bytes.Contains(origData, jobSpecTemplatecreationTs) hasNullStatus = bytes.Contains(origData, nullStatus) hasNullReplicaStatus = bytes.Contains(origData, nullReplicaStatus) hasNullLBStatus = bytes.Contains(origData, nullLBStatus) hasNullMetaStatus = bytes.Contains(origData, nullMetaStatus) } // null value is false in case of origFile if !hasObjectMetacreationTs { finalData = bytes.Replace(finalData, objectMetacreationTs, []byte("\n"), -1) } if !hasSpecTemplatecreationTs { finalData = bytes.Replace(finalData, specTemplatecreationTs, []byte("\n"), -1) } if !hasJobSpecTemplatecreationTs { finalData = bytes.Replace(finalData, jobSpecTemplatecreationTs, []byte("\n"), -1) } if !hasNullStatus { finalData = bytes.Replace(finalData, nullStatus, []byte("\n"), -1) } if !hasNullReplicaStatus { finalData = bytes.Replace(finalData, nullReplicaStatus, []byte("\n"), -1) } if !hasNullLBStatus { finalData = bytes.Replace(finalData, nullLBStatus, []byte("\n"), -1) } if !hasNullMetaStatus { finalData = bytes.Replace(finalData, nullMetaStatus, []byte("\n"), -1) } return finalData, nil } 07070100000117000081A400000000000000000000000166C635DC0000052B000000000000000000000000000000000000001D00000000kubeaudit-0.22.2/fix_test.gopackage kubeaudit_test import ( "os" "path/filepath" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/config" "github.com/Shopify/kubeaudit/internal/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Test that fixing all fixtures in auditors/* results in manifests that pass all audits func TestFix(t *testing.T) { auditorDirs, err := os.ReadDir("auditors") if !assert.Nil(t, err) { return } allAuditors, err := all.Auditors(config.KubeauditConfig{}) require.NoError(t, err) for _, auditorDir := range auditorDirs { if !auditorDir.IsDir() { continue } fixturesDirPath := filepath.Join("..", auditorDir.Name(), "fixtures") fixtureFiles, err := os.ReadDir(fixturesDirPath) if os.IsNotExist(err) { continue } if !assert.Nil(t, err) { return } for _, fixture := range fixtureFiles { t.Run(filepath.Join(fixturesDirPath, fixture.Name()), func(t *testing.T) { _, report := test.FixSetupMultiple(t, fixturesDirPath, fixture.Name(), allAuditors) for _, result := range report.Results() { for _, auditResult := range result.GetAuditResults() { if !assert.NotEqual(t, kubeaudit.Error, auditResult.Severity) { return } } } }) } } } 07070100000118000081A400000000000000000000000166C635DC00000CAB000000000000000000000000000000000000001800000000kubeaudit-0.22.2/go.modmodule github.com/Shopify/kubeaudit require ( github.com/jetstack/cert-manager v1.6.1 github.com/owenrumney/go-sarif/v2 v2.1.2 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.24.3 k8s.io/apiextensions-apiserver v0.23.5 k8s.io/apimachinery v0.24.4 k8s.io/client-go v0.24.3 ) require ( cloud.google.com/go v0.99.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.19 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful v2.9.5+incompatible // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-logr/logr v1.2.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.4.0 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.60.1 // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) go 1.22.1 07070100000119000081A400000000000000000000000166C635DC00019259000000000000000000000000000000000000001800000000kubeaudit-0.22.2/go.sumcloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.19 h1:7/IqD2fEYVha1EPeaiytVKhzmPV223pfkRIQUGOK2IE= github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jetstack/cert-manager v1.6.1 h1:VME4bVID2gVTfebO5X4Nq9FvKvvi3+VLcA0mmtYlKuw= github.com/jetstack/cert-manager v1.6.1/go.mod h1:1nXjnzzsYcIFvl4eLTkVqpvh9NQogkCq4FaCmgvNDDY= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= github.com/owenrumney/go-sarif/v2 v2.1.2 h1:PMDK7tXShJ9zsB7bfvlpADH5NEw1dfA9xwU8Xtdj73U= github.com/owenrumney/go-sarif/v2 v2.1.2/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY= k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI= k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ= k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apimachinery v0.24.4 h1:S0Ur3J/PbivTcL43EdSdPhqCqKla2NIuneNwZcTDeGQ= k8s.io/apimachinery v0.24.4/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw= k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 0707010000011A000081A400000000000000000000000166C635DC000003D0000000000000000000000000000000000000002700000000kubeaudit-0.22.2/goreleaser.DockerfileFROM golang:1.22.1 AS builder # no need to include cgo bindings ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 # add ca certificates and timezone data files # hadolint ignore=DL3008 RUN apt-get install --yes --no-install-recommends ca-certificates tzdata # add unprivileged user RUN adduser --shell /bin/true --uid 1000 --disabled-login --no-create-home --gecos '' app \ && sed -i -r "/^(app|root)/!d" /etc/group /etc/passwd \ && sed -i -r 's#^(.*):[^:]*$#\1:/sbin/nologin#' /etc/passwd # # --- # # start with empty image FROM scratch # add-in our timezone data file COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo # add-in our unprivileged user COPY --from=builder /etc/passwd /etc/group /etc/shadow /etc/ # add-in our ca certificates COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # add-in our application COPY kubeaudit / # from now on, run as the unprivileged user USER 1000 # entrypoint ENTRYPOINT ["/kubeaudit"] CMD ["all"] 0707010000011B000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001A00000000kubeaudit-0.22.2/internal0707010000011C000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002000000000kubeaudit-0.22.2/internal/color0707010000011D000081A400000000000000000000000166C635DC00000420000000000000000000000000000000000000002900000000kubeaudit-0.22.2/internal/color/color.gopackage color import "runtime" var Reset = "\033[0m" var RedColor = "\033[31m" var GreenColor = "\033[32m" var YellowColor = "\033[33m" var BlueColor = "\033[34m" var PurpleColor = "\033[35m" var CyanColor = "\033[36m" var GrayColor = "\033[37m" var WhiteColor = "\033[97m" func Red(s string) string { return Colored(RedColor, s) } func Green(s string) string { return Colored(GreenColor, s) } func Yellow(s string) string { return Colored(YellowColor, s) } func Blue(s string) string { return Colored(BlueColor, s) } func Purple(s string) string { return Colored(PurpleColor, s) } func Cyan(s string) string { return Colored(CyanColor, s) } func Gray(s string) string { return Colored(GrayColor, s) } func White(s string) string { return Colored(WhiteColor, s) } func Colored(color, s string) string { return color + s + Reset } func init() { if runtime.GOOS == "windows" { Reset = "" RedColor = "" GreenColor = "" YellowColor = "" BlueColor = "" PurpleColor = "" CyanColor = "" GrayColor = "" WhiteColor = "" } } 0707010000011E000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002600000000kubeaudit-0.22.2/internal/k8sinternal0707010000011F000081A400000000000000000000000166C635DC00001C1F000000000000000000000000000000000000003000000000kubeaudit-0.22.2/internal/k8sinternal/client.gopackage k8sinternal import ( "context" "errors" "os" "github.com/Shopify/kubeaudit/pkg/k8s" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" // add authentication support to the kubernetes code _ "k8s.io/client-go/plugin/pkg/client/auth/azure" _ "k8s.io/client-go/plugin/pkg/client/auth/exec" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) // ErrNoReadableKubeConfig represents any error that prevents the client from opening a kubeconfig file. var ErrNoReadableKubeConfig = errors.New("unable to open kubeconfig file") var DefaultClient = k8sClient{} // Client abstracts the API to allow testing. type Client interface { InClusterConfig() (*rest.Config, error) } // k8sClient wraps kubernetes client-go so it can be mocked. type k8sClient struct{} // InClusterConfig wraps the client-go method with the same name. func (kc k8sClient) InClusterConfig() (*rest.Config, error) { return rest.InClusterConfig() } // NewKubeClientLocal creates a new kube client for local mode func NewKubeClientLocal(configPath string, context string) (KubeClient, error) { var kubeconfig *rest.Config var err error if configPath == "" { kubeconfig, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{CurrentContext: context, ClusterInfo: clientcmdapi.Cluster{Server: ""}}, ).ClientConfig() } else { if _, err = os.Stat(configPath); err != nil { return nil, ErrNoReadableKubeConfig } kubeconfig, err = clientcmd.BuildConfigFromFlags("", configPath) } if err != nil { return nil, err } // Ignore warnings from kubeclient as they are expected to be reported by the deprecatedapi auditor. kubeconfig.WarningHandler = rest.NoWarnings{} return newKubeClientFromConfig(kubeconfig) } // NewKubeClientCluster creates a new kube client for cluster mode func NewKubeClientCluster(client Client) (KubeClient, error) { config, err := client.InClusterConfig() if err != nil { return nil, err } log.Info("Running inside cluster, using the cluster config") return newKubeClientFromConfig(config) } // newKubeClientFromConfig creates a new dynamic client with discovery or returns an error. func newKubeClientFromConfig(config *rest.Config) (KubeClient, error) { discovery, err := discovery.NewDiscoveryClientForConfig(config) if err != nil { return nil, err } dynamic, err := dynamic.NewForConfig(config) if err != nil { return nil, err } return NewKubeClient(dynamic, discovery), nil } // IsRunningInCluster returns true if kubeaudit is running inside a cluster func IsRunningInCluster(client Client) bool { _, err := client.InClusterConfig() return err == nil } type ClientOptions struct { // Namespace filters resources by namespace. Defaults to all namespaces. Namespace string // IncludeGenerated is a boolean option to include generated resources. IncludeGenerated bool } type KubeClient interface { // GetAllResources gets all supported resources from the cluster GetAllResources(options ClientOptions) ([]k8s.Resource, error) // GetKubernetesVersion returns the kubernetes client version GetKubernetesVersion() (*version.Info, error) // ServerPreferredResources returns the supported resources with the version preferred by the server. ServerPreferredResources() ([]*metav1.APIResourceList, error) } type kubeClient struct { dynamicClient dynamic.Interface discoveryClient discovery.DiscoveryInterface } func NewKubeClient(dynamic dynamic.Interface, discovery discovery.DiscoveryInterface) KubeClient { return &kubeClient{dynamicClient: dynamic, discoveryClient: discovery} } // GetAllResources gets all supported resources from the cluster func (kc kubeClient) GetAllResources(options ClientOptions) ([]k8s.Resource, error) { var resources []k8s.Resource lists, err := kc.ServerPreferredResources() if err != nil { return nil, err } for _, list := range lists { if list == nil || len(list.APIResources) == 0 { continue } gv, err := schema.ParseGroupVersion(list.GroupVersion) if err != nil { continue } for _, apiresource := range list.APIResources { if len(apiresource.Verbs) == 0 { continue } gvr := schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: apiresource.Name} // Namespace has to be included as a resource to audit if it is specified. if apiresource.Name == "namespaces" && options.Namespace != "" { unstructured, err := kc.dynamicClient.Resource(gvr).Get(context.Background(), options.Namespace, metav1.GetOptions{}) if err == nil { r, err := unstructuredToObject(unstructured) if err == nil { resources = append(resources, r) } } } else { unstructuredList, err := kc.dynamicClient.Resource(gvr).Namespace(options.Namespace).List(context.Background(), metav1.ListOptions{}) if err == nil { for _, unstructured := range unstructuredList.Items { r, err := unstructuredToObject(&unstructured) if err == nil { resources = append(resources, r) } } } } } } if !options.IncludeGenerated { resources = excludeGenerated(resources) } return resources, nil } // unstructuredToObject unstructured to Go typed object conversions func unstructuredToObject(unstructured *unstructured.Unstructured) (k8s.Resource, error) { obj, err := scheme.New(unstructured.GroupVersionKind()) if err == nil { err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.UnstructuredContent(), obj) } return obj, err } // excludeGenerated filters out generated resources (eg. pods generated by deployments) func excludeGenerated(resources []k8s.Resource) []k8s.Resource { var filteredResources []k8s.Resource for _, resource := range resources { if resource != nil { obj, _ := resource.(metav1.ObjectMetaAccessor) if obj != nil { meta := obj.GetObjectMeta() if meta != nil { if len(meta.GetOwnerReferences()) == 0 { filteredResources = append(filteredResources, resource) } } } } } return filteredResources } // GetKubernetesVersion returns the kubernetes client version func (kc kubeClient) GetKubernetesVersion() (*version.Info, error) { return kc.discoveryClient.ServerVersion() } // ServerPreferredResources returns the supported resources with the version preferred by the server. func (kc kubeClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) { list, err := discovery.ServerPreferredResources(kc.discoveryClient) // If a group is not served by the cluster the resources of this group will not be audited. var e *discovery.ErrGroupDiscoveryFailed if errors.As(err, &e) { return list, nil } return list, err } 07070100000120000081A400000000000000000000000166C635DC00001CDF000000000000000000000000000000000000003500000000kubeaudit-0.22.2/internal/k8sinternal/client_test.gopackage k8sinternal_test import ( "errors" "testing" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" fakediscovery "k8s.io/client-go/discovery/fake" fakedynamic "k8s.io/client-go/dynamic/fake" fakeclientset "k8s.io/client-go/kubernetes/fake" _ "k8s.io/client-go/plugin/pkg/client/auth/azure" // auth for AKS clusters _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // auth for GKE clusters _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" // auth for OIDC "k8s.io/client-go/rest" ) type MockK8sClient struct { mock.Mock } func (kc *MockK8sClient) InClusterConfig() (*rest.Config, error) { args := kc.Called() return args.Get(0).(*rest.Config), args.Error(1) } func TestKubeClientConfigLocal(t *testing.T) { assert := assert.New(t) _, err := k8sinternal.NewKubeClientLocal("/notarealfile", "") assert.Equal(k8sinternal.ErrNoReadableKubeConfig, err) _, err = k8sinternal.NewKubeClientLocal("client.go", "") assert.NotEqual(k8sinternal.ErrNoReadableKubeConfig, err) assert.NotNil(err) } func TestKubeClientConfigCluster(t *testing.T) { assert := assert.New(t) client := &MockK8sClient{} var config *rest.Config = nil client.On("InClusterConfig").Return(config, errors.New("mock error")) kubeclient, err := k8sinternal.NewKubeClientCluster(client) assert.Nil(kubeclient) assert.NotNil(err) client = &MockK8sClient{} client.On("InClusterConfig").Return(&rest.Config{}, nil) kubeclient, err = k8sinternal.NewKubeClientCluster(client) assert.NotNil(kubeclient) assert.NoError(err) } func TestIsRunningInCluster(t *testing.T) { assert := assert.New(t) client := &MockK8sClient{} var config *rest.Config = nil client.On("InClusterConfig").Return(config, errors.New("mock error")) assert.False(k8sinternal.IsRunningInCluster(client)) client = &MockK8sClient{} client.On("InClusterConfig").Return(&rest.Config{}, nil) assert.True(k8sinternal.IsRunningInCluster(client)) } func TestGetAllResources(t *testing.T) { resourceTemplates := []k8s.Resource{ k8s.NewDeployment(), k8s.NewPod(), k8s.NewNamespace(), k8s.NewDaemonSet(), k8s.NewNetworkPolicy(), k8s.NewReplicationController(), k8s.NewStatefulSet(), k8s.NewPodTemplate(), k8s.NewCronJob(), k8s.NewServiceAccount(), k8s.NewService(), k8s.NewJob(), } namespaces := []string{"foo", "bar"} resources := make([]runtime.Object, 0, len(resourceTemplates)*len(namespaces)) for _, template := range resourceTemplates { for _, namespace := range namespaces { resource := template.DeepCopyObject() setNamespace(resource, namespace) resources = append(resources, resource) } } client := newFakeKubeClient(resources...) k8sresources, err := client.GetAllResources(k8sinternal.ClientOptions{}) require.NoError(t, err) assert.Len(t, k8sresources, len(resourceTemplates)*len(namespaces)) k8sresources, err = client.GetAllResources(k8sinternal.ClientOptions{Namespace: namespaces[0]}) require.NoError(t, err) assert.Len(t, k8sresources, len(resourceTemplates)) } func setNamespace(resource k8s.Resource, namespace string) { if _, ok := resource.(*k8s.NamespaceV1); ok { k8s.GetObjectMeta(resource).SetName(namespace) } else { k8s.GetObjectMeta(resource).SetNamespace(namespace) } } func TestGetKubernetesVersion(t *testing.T) { serverVersion := &version.Info{ Major: "0", Minor: "0", GitCommit: "0000", Platform: "ACME 8-bit", } client := newFakeKubeClientWithServerVersion(serverVersion) r, err := client.GetKubernetesVersion() assert.Nil(t, err) assert.EqualValues(t, *serverVersion, *r) } func TestIncludeGenerated(t *testing.T) { // The "IncludeGenerated" option only applies to local and cluster mode if !test.UseKind() { return } namespace := "include-generated" defer test.DeleteNamespace(t, namespace) test.CreateNamespace(t, namespace) test.ApplyManifest(t, "./fixtures/include-generated.yml", namespace) client, err := k8sinternal.NewKubeClientLocal("", "") require.NoError(t, err) // Test IncludeGenerated = false resources, err := client.GetAllResources( k8sinternal.ClientOptions{Namespace: namespace, IncludeGenerated: false}, ) require.NoError(t, err) assert.False(t, hasPod(resources), "Expected no pods for IncludeGenerated=false") // Test IncludeGenerated unspecified defaults to false resources, err = client.GetAllResources( k8sinternal.ClientOptions{Namespace: namespace}, ) require.NoError(t, err) assert.False(t, hasPod(resources), "Expected no pods if IncludeGenerated is unspecified (ie. default to false)") // Test IncludeGenerated = true resources, err = client.GetAllResources( k8sinternal.ClientOptions{Namespace: namespace, IncludeGenerated: true}, ) require.NoError(t, err) assert.True(t, hasPod(resources), "Expected pods for IncludeGenerated=true") } func hasPod(resources []k8s.Resource) bool { for _, resource := range resources { if k8s.IsPodV1(resource) { return true } } return false } func newFakeKubeClient(resources ...runtime.Object) k8sinternal.KubeClient { return newFakeKubeClientWithServerVersion(nil, resources...) } func newFakeKubeClientWithServerVersion(serverversion *version.Info, resources ...runtime.Object) k8sinternal.KubeClient { clientset := fakeclientset.NewSimpleClientset() fakeDiscovery, _ := clientset.Discovery().(*fakediscovery.FakeDiscovery) if serverversion != nil { fakeDiscovery.FakedServerVersion = serverversion } unstructuredresources := []runtime.Object{} gvrToListKind := map[schema.GroupVersionResource]string{} gvAPIResources := map[string][]metav1.APIResource{} for _, r := range resources { gvk := r.GetObjectKind().GroupVersionKind() listGVK := gvk listGVK.Kind += "List" u := unstructured.Unstructured{} u.SetGroupVersionKind(r.GetObjectKind().GroupVersionKind()) u.SetName(k8s.GetObjectMeta(r).GetName()) u.SetNamespace(k8s.GetObjectMeta(r).GetNamespace()) unstructuredresources = (append(unstructuredresources, &u)) kind := r.GetObjectKind().GroupVersionKind().Kind plural, _ := meta.UnsafeGuessKindToResource(r.GetObjectKind().GroupVersionKind()) apiresource := metav1.APIResource{Name: plural.Resource, Namespaced: false, Group: gvk.Group, Version: gvk.Version, Kind: kind, Verbs: metav1.Verbs{"list"}} gvr := schema.GroupVersionResource{Group: apiresource.Group, Version: apiresource.Version, Resource: apiresource.Name} if _, ok := gvrToListKind[gvr]; !ok { gvrToListKind[gvr] = kind + "List" gv := gvk.GroupVersion().String() gvAPIResources[gv] = append(gvAPIResources[gv], apiresource) } } for gv, apiresources := range gvAPIResources { fakeDiscovery.Resources = append(fakeDiscovery.Resources, &metav1.APIResourceList{ GroupVersion: gv, APIResources: apiresources}) } fakedynamic := fakedynamic.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, unstructuredresources...) return k8sinternal.NewKubeClient(fakedynamic, fakeDiscovery) } 07070100000121000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002F00000000kubeaudit-0.22.2/internal/k8sinternal/fixtures07070100000122000081A400000000000000000000000166C635DC0000010C000000000000000000000000000000000000004500000000kubeaudit-0.22.2/internal/k8sinternal/fixtures/include-generated.ymlapiVersion: apps/v1 kind: Deployment metadata: name: deployment spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: containers: - name: container image: scratch 07070100000123000081A400000000000000000000000166C635DC00000107000000000000000000000000000000000000004600000000kubeaudit-0.22.2/internal/k8sinternal/fixtures/test-encode-decode.ymlapiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null namespace: foo spec: selector: null strategy: {} template: metadata: creationTimestamp: null spec: containers: - name: bar resources: {} status: {} 07070100000124000081A400000000000000000000000166C635DC000002DC000000000000000000000000000000000000003100000000kubeaudit-0.22.2/internal/k8sinternal/runtime.gopackage k8sinternal import ( "github.com/Shopify/kubeaudit/pkg/k8s" k8sRuntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) func DecodeResource(b []byte) (k8s.Resource, error) { decoder := codecs.UniversalDeserializer() return k8sRuntime.Decode(decoder, b) } func EncodeResource(resource k8s.Resource) ([]byte, error) { info, _ := k8sRuntime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/yaml") groupVersion := schema.GroupVersion{Group: resource.GetObjectKind().GroupVersionKind().Group, Version: resource.GetObjectKind().GroupVersionKind().Version} encoder := codecs.EncoderForVersion(info.Serializer, groupVersion) return k8sRuntime.Encode(encoder, resource) } 07070100000125000081A400000000000000000000000166C635DC00000EB7000000000000000000000000000000000000003600000000kubeaudit-0.22.2/internal/k8sinternal/runtime_test.gopackage k8sinternal_test import ( "bytes" "os" "path" "testing" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/internal/test" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewTrue(t *testing.T) { assert.True(t, *k8s.NewTrue()) } func TestNewFalse(t *testing.T) { assert.False(t, *k8s.NewFalse()) } func TestEncodeDecode(t *testing.T) { assert := assert.New(t) require := require.New(t) deployment := k8s.NewDeployment() deployment.ObjectMeta = k8s.ObjectMetaV1{Namespace: "foo"} deployment.Spec.Template.Spec.Containers = []k8s.ContainerV1{{Name: "bar"}} expectedManifest, err := os.ReadFile("fixtures/test-encode-decode.yml") require.NoError(err) encoded, err := k8sinternal.EncodeResource(deployment) require.NoError(err) assert.Equal(string(expectedManifest), string(encoded)) decoded, err := k8sinternal.DecodeResource(expectedManifest) require.NoError(err) assert.Equal(deployment, decoded) } func TestGetContainers(t *testing.T) { for _, resource := range getAllResources(t) { containers := k8s.GetContainers(resource) switch resource.(type) { case *k8s.NamespaceV1: assert.Nil(t, containers) default: assert.NotEmpty(t, containers) } } } func TestGetAnnotations(t *testing.T) { annotations := map[string]string{"foo": "bar"} deployment := k8s.NewDeployment() deployment.Spec.Template.ObjectMeta.SetAnnotations(annotations) assert.Equal(t, annotations, k8s.GetAnnotations(deployment)) } func TestGetLabels(t *testing.T) { labels := map[string]string{"foo": "bar"} deployment := k8s.NewDeployment() deployment.Spec.Template.ObjectMeta.SetLabels(labels) assert.Equal(t, labels, k8s.GetLabels(deployment)) } func TestGetObjectMeta(t *testing.T) { assert := assert.New(t) objectMeta := k8s.ObjectMetaV1{Name: "foo"} podObjectMeta := k8s.ObjectMetaV1{Name: "bar"} deployment := k8s.NewDeployment() deployment.ObjectMeta = objectMeta deployment.Spec.Template.ObjectMeta = podObjectMeta assert.Equal(&objectMeta, k8s.GetObjectMeta(deployment)) assert.Equal(&podObjectMeta, k8s.GetPodObjectMeta(deployment)) pod := k8s.NewPod() pod.ObjectMeta = objectMeta assert.Equal(&objectMeta, k8s.GetObjectMeta(pod)) assert.Equal(&objectMeta, k8s.GetPodObjectMeta(pod)) namespace := k8s.NewNamespace() namespace.ObjectMeta = objectMeta assert.Equal(&objectMeta, k8s.GetObjectMeta(namespace)) assert.Equal(&objectMeta, k8s.GetPodObjectMeta(namespace)) } func TestGetPodTemplateSpec(t *testing.T) { for _, resource := range getAllResources(t) { podTemplateSpec := k8s.GetPodTemplateSpec(resource) switch resource.(type) { case *k8s.PodV1, *k8s.NamespaceV1: assert.Nil(t, podTemplateSpec) default: assert.NotNil(t, podTemplateSpec) } } } func TestUnsupportedResource(t *testing.T) { unsupported := &k8s.UnsupportedType{} assert.Nil(t, k8s.GetAnnotations(unsupported)) assert.Nil(t, k8s.GetLabels(unsupported)) assert.Nil(t, k8s.GetContainers(unsupported)) } func getAllResources(t *testing.T) (resources []k8s.Resource) { fixtureDir := "../test/fixtures/all_resources" for _, fixture := range test.GetAllFileNames(t, fixtureDir) { resources = append(resources, getResourcesFromManifest(t, path.Join(fixtureDir, fixture))...) } return } func getResourcesFromManifest(t *testing.T, manifest string) (resources []k8s.Resource) { assert := assert.New(t) data, err := os.ReadFile(manifest) require.NoError(t, err) bufSlice := bytes.Split(data, []byte("---")) for _, b := range bufSlice { resource, err := k8sinternal.DecodeResource(b) if err != nil { continue } assert.NotNil(resource) resources = append(resources, resource) } return } 07070100000126000081A400000000000000000000000166C635DC000003F8000000000000000000000000000000000000003000000000kubeaudit-0.22.2/internal/k8sinternal/scheme.gopackage k8sinternal import ( certmanagerv1alpha2 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" kubescheme "k8s.io/client-go/kubernetes/scheme" ) var scheme = kubescheme.Scheme var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ certmanagerv1alpha2.AddToScheme, apiextensionsv1.AddToScheme, apiextensionsv1beta1.AddToScheme, } // AddToScheme adds localScheme to Scheme var addToScheme = localSchemeBuilder.AddToScheme func init() { v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) utilruntime.Must(addToScheme(scheme)) } 07070100000127000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002000000000kubeaudit-0.22.2/internal/sarif07070100000128000081A400000000000000000000000166C635DC000007C8000000000000000000000000000000000000002900000000kubeaudit-0.22.2/internal/sarif/rules.gopackage sarif import ( "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/asat" "github.com/Shopify/kubeaudit/auditors/capabilities" "github.com/Shopify/kubeaudit/auditors/deprecatedapis" "github.com/Shopify/kubeaudit/auditors/hostns" "github.com/Shopify/kubeaudit/auditors/image" "github.com/Shopify/kubeaudit/auditors/limits" "github.com/Shopify/kubeaudit/auditors/mounts" "github.com/Shopify/kubeaudit/auditors/netpols" "github.com/Shopify/kubeaudit/auditors/nonroot" "github.com/Shopify/kubeaudit/auditors/privesc" "github.com/Shopify/kubeaudit/auditors/privileged" "github.com/Shopify/kubeaudit/auditors/rootfs" "github.com/Shopify/kubeaudit/auditors/seccomp" ) var allAuditors = map[string]string{ apparmor.Name: "Finds containers that do not have AppArmor enabled", asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", deprecatedapis.Name: "Finds any resource defined with a deprecated API version", hostns.Name: "Finds containers that have HostPID, HostIPC or HostNetwork enabled", image.Name: "Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag", limits.Name: "Finds containers which exceed the specified CPU and memory limits or do not specify any", mounts.Name: "Finds containers that have sensitive host paths mounted", netpols.Name: "Finds namespaces that do not have a default-deny network policy", nonroot.Name: "Finds containers allowed to run as root", privesc.Name: "Finds containers that allow privilege escalation", privileged.Name: "Finds containers running as privileged", rootfs.Name: "Finds containers which do not have a read-only filesystem", seccomp.Name: "Finds containers running without seccomp", } 07070100000129000081A400000000000000000000000166C635DC000001C9000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/internal/sarif/rules_test.gopackage sarif import ( "testing" "github.com/Shopify/kubeaudit/auditors/all" "github.com/stretchr/testify/assert" ) func TestAuditorsLengthAndDescription(t *testing.T) { // if new auditors are created // make sure they're added with a matching description for _, auditorName := range all.AuditorNames { description, ok := allAuditors[auditorName] assert.Truef(t, ok && description != "", "missing description for auditor %s", auditorName) } } 0707010000012A000081A400000000000000000000000166C635DC00000E64000000000000000000000000000000000000002900000000kubeaudit-0.22.2/internal/sarif/sarif.gopackage sarif import ( "bytes" "encoding/json" "fmt" "strings" "github.com/Shopify/kubeaudit" "github.com/owenrumney/go-sarif/v2/sarif" ) const repoURL = "https://github.com/Shopify/kubeaudit" // Create generates new sarif Report or returns an error func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // create a new report object report, err := sarif.New(sarif.Version210) if err != nil { return nil, err } // create a run for kubeaudit run := sarif.NewRunWithInformationURI("kubeaudit", repoURL) report.AddRun(run) var results []*kubeaudit.AuditResult for _, reportResult := range kubeauditReport.Results() { r := reportResult.GetAuditResults() results = append(results, r...) } for _, result := range results { severityLevel := result.Severity.String() auditor := strings.ToLower(result.Auditor) var metadataTxt string if len(result.Metadata) > 0 { formattedMap := make(map[string]string) for k, v := range result.Metadata { formattedMap[k] = v } metadata, jsonErr := json.Marshal(formattedMap) if jsonErr != nil { metadata = []byte(jsonErr.Error()) } metadataTxt = fmt.Sprintf("Metadata: %s\n", string(metadata)) } docsURL := "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" helpText := fmt.Sprintf("Type: kubernetes\nAuditor Docs: To find out more about the issue and how to fix it, follow [this link](%s)\nDescription: %s\n%s\n\n Note: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit ", docsURL, allAuditors[auditor], metadataTxt) helpMarkdown := fmt.Sprintf("**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](%s)\n**Description:** %s\n **Metadata**: %s\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit ", docsURL, allAuditors[auditor], metadataTxt) // we only add rules to the report based on the result findings run.AddRule(result.Rule). WithName(result.Auditor). WithHelp(&sarif.MultiformatMessageString{Text: &helpText, Markdown: &helpMarkdown}). WithShortDescription(&sarif.MultiformatMessageString{Text: &result.Rule}). WithProperties(sarif.Properties{ "tags": []string{ "security", "kubernetes", "infrastructure", }, }) // SARIF specifies the following severity levels: warning, error, note and none // https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html // so we're converting info to note here so we get valid SARIF output if result.Severity.String() == kubeaudit.Info.String() { severityLevel = "note" } details := fmt.Sprintf("Details: %s\n Auditor: %s\nDescription: %s\nAuditor docs: %s ", result.Message, result.Auditor, allAuditors[auditor], docsURL) location := sarif.NewPhysicalLocation(). WithArtifactLocation(sarif.NewSimpleArtifactLocation(result.FilePath).WithUriBaseId("ROOTPATH")). WithRegion(sarif.NewRegion().WithStartLine(1)) result := sarif.NewRuleResult(result.Rule). WithMessage(sarif.NewTextMessage(details)). WithLevel(severityLevel). WithLocations([]*sarif.Location{sarif.NewLocation().WithPhysicalLocation(location)}) run.AddResult(result) } var reportBytes bytes.Buffer err = report.Write(&reportBytes) if err != nil { return nil, nil } return report, nil } 0707010000012B000081A400000000000000000000000166C635DC00000F61000000000000000000000000000000000000002E00000000kubeaudit-0.22.2/internal/sarif/sarif_test.gopackage sarif import ( "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/capabilities" "github.com/Shopify/kubeaudit/auditors/image" "github.com/Shopify/kubeaudit/auditors/limits" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCreateWithResults(t *testing.T) { cases := []struct { description string auditResults []*kubeaudit.AuditResult expectedRule string expectedErrorLevel string expectedMessage string expectedURI string expectedFilePath string }{ { "apparmor invalid", []*kubeaudit.AuditResult{{ Auditor: apparmor.Name, Rule: apparmor.AppArmorInvalidAnnotation, Severity: kubeaudit.Error, Message: "AppArmor annotation key refers to a container that doesn't exist", FilePath: "apparmorPath", }}, apparmor.AppArmorInvalidAnnotation, "error", "AppArmor annotation key refers to a container that doesn't exist", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md", "apparmorPath", }, { "capabilities added", []*kubeaudit.AuditResult{{ Auditor: capabilities.Name, Rule: capabilities.CapabilityAdded, Severity: kubeaudit.Error, Message: "It should be removed from the capability add list", FilePath: "capsPath", }}, capabilities.CapabilityAdded, "error", "It should be removed from the capability add list", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/capabilities.md", "capsPath", }, { "image tag is present", []*kubeaudit.AuditResult{{ Auditor: image.Name, Rule: image.ImageCorrect, Severity: kubeaudit.Info, Message: "Image tag is correct", FilePath: "imagePath", }}, image.ImageCorrect, "note", "Image tag is correct", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/image.md", "imagePath", }, { "limits is nil", []*kubeaudit.AuditResult{{ Auditor: limits.Name, Rule: limits.LimitsNotSet, Severity: kubeaudit.Warn, Message: "Resource limits not set", FilePath: "limitsPath", }}, limits.LimitsNotSet, "warning", "Resource limits not set", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/limits.md", "limitsPath", }, } for _, tc := range cases { t.Run(tc.description, func(t *testing.T) { kubeAuditReport := kubeaudit.NewReport([]kubeaudit.Result{&kubeaudit.WorkloadResult{ AuditResults: tc.auditResults, }}) sarifReport, err := Create(kubeAuditReport) require.NoError(t, err) assert.Equal(t, repoURL, *sarifReport.Runs[0].Tool.Driver.InformationURI) // verify that the rules have been added as per report findings assert.Equal(t, tc.expectedRule, sarifReport.Runs[0].Tool.Driver.Rules[0].ID) var ruleNames []string //check for rules occurrences for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { assert.Equal(t, []string{ "security", "kubernetes", "infrastructure", }, sarifRule.Properties["tags"], ) ruleNames = append(ruleNames, sarifRule.ID) assert.Contains(t, *sarifRule.Help.Text, tc.expectedURI) } for _, sarifResult := range sarifReport.Runs[0].Results { assert.Contains(t, ruleNames, *sarifResult.RuleID) assert.Equal(t, tc.expectedErrorLevel, *sarifResult.Level) assert.Contains(t, *sarifResult.Message.Text, tc.expectedMessage) assert.Contains(t, tc.expectedFilePath, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI) } }) } } func TestCreateWithNoResults(t *testing.T) { sarifReport, err := Create(&kubeaudit.Report{}) require.NoError(t, err) require.NotEmpty(t, *sarifReport.Runs[0]) // verify that the rules are only added as per report findings assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, 0) } 0707010000012C000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001F00000000kubeaudit-0.22.2/internal/test0707010000012D000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000002800000000kubeaudit-0.22.2/internal/test/fixtures0707010000012E000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000003600000000kubeaudit-0.22.2/internal/test/fixtures/all_resources0707010000012F000081A400000000000000000000000166C635DC000001A8000000000000000000000000000000000000004200000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/cronjob.ymlapiVersion: v1 kind: Namespace metadata: name: cronjob --- apiVersion: batch/v1beta1 kind: CronJob metadata: name: cronjob namespace: cronjob spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: restartPolicy: Never hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000130000081A400000000000000000000000166C635DC000001A8000000000000000000000000000000000000004700000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/daemonset-v1.ymlapiVersion: v1 kind: Namespace metadata: name: daemonset-v1 --- apiVersion: apps/v1 kind: DaemonSet metadata: name: daemonset1 namespace: daemonset-v1 spec: selector: matchLabels: name: daemonset1 template: metadata: labels: name: daemonset1 spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000131000081A400000000000000000000000166C635DC000001B5000000000000000000000000000000000000004D00000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/deployment-apps-v1.ymlapiVersion: v1 kind: Namespace metadata: name: deployment-apps-v1 --- apiVersion: apps/v1 kind: Deployment metadata: name: deployment namespace: deployment-apps-v1 spec: selector: matchLabels: name: deployment template: metadata: labels: name: deployment spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000132000081A400000000000000000000000166C635DC0000013C000000000000000000000000000000000000003E00000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/job.ymlapiVersion: v1 kind: Namespace metadata: name: job --- apiVersion: batch/v1 kind: Job metadata: name: job namespace: job spec: template: spec: restartPolicy: Never hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000133000081A400000000000000000000000166C635DC000000ED000000000000000000000000000000000000003E00000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/pod.ymlapiVersion: v1 kind: Namespace metadata: name: pod --- apiVersion: v1 kind: Pod metadata: name: pod namespace: pod spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000134000081A400000000000000000000000166C635DC00000157000000000000000000000000000000000000004600000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/podtemplate.ymlapiVersion: v1 kind: Namespace metadata: name: podtemplate --- apiVersion: v1 kind: PodTemplate metadata: name: templatespec namespace: podtemplate template: metadata: labels: name: templatespec spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000135000081A400000000000000000000000166C635DC000001A3000000000000000000000000000000000000005000000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/replicationcontroller.ymlapiVersion: v1 kind: Namespace metadata: name: replicationcontroller --- apiVersion: v1 kind: ReplicationController metadata: name: replicationcontroller namespace: replicationcontroller spec: template: metadata: labels: name: replicationcontroller spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000136000081A400000000000000000000000166C635DC000001CC000000000000000000000000000000000000004900000000kubeaudit-0.22.2/internal/test/fixtures/all_resources/statefulset-v1.ymlapiVersion: v1 kind: Namespace metadata: name: statefulset-v1 --- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset namespace: statefulset-v1 spec: serviceName: statefulset selector: matchLabels: name: statefulset template: metadata: labels: name: statefulset spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: container image: scratch 07070100000137000081A400000000000000000000000166C635DC00000155000000000000000000000000000000000000004700000000kubeaudit-0.22.2/internal/test/fixtures/custom_resource_definition.ymlapiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: wgnodes.wormhole.gravitational.io spec: group: wormhole.gravitational.io names: kind: Wgnode plural: wgnodes scope: Namespaced version: v1beta1 status: acceptedNames: kind: "" plural: "" conditions: [] storedVersions: [] 07070100000138000081A400000000000000000000000166C635DC000000D3000000000000000000000000000000000000003400000000kubeaudit-0.22.2/internal/test/fixtures/service.ymlapiVersion: v1 kind: Service metadata: name: test-service spec: selector: app: test-service ports: - protocol: TCP port: 80 targetPort: 9376 clusterIP: 10.96.0.1 type: LoadBalancer 07070100000139000081A400000000000000000000000166C635DC00000108000000000000000000000000000000000000004200000000kubeaudit-0.22.2/internal/test/fixtures/unknown_resource_type.ymlapiVersion: networking.k8s.io kind: Ingress metadata: name: unknown_resource_type spec: rules: - http: paths: - path: /test-unknownpath backend: service: name: test-unknown port: number: 80 0707010000013A000081A400000000000000000000000166C635DC000014CC000000000000000000000000000000000000002700000000kubeaudit-0.22.2/internal/test/test.gopackage test import ( "bytes" "fmt" "os" "os/exec" "path/filepath" "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // SharedFixturesDir contains fixtures used by multiple tests const SharedFixturesDir = "../../internal/test/fixtures" const MANIFEST_MODE = "manifest" const LOCAL_MODE = "local" func AuditManifest(t *testing.T, fixtureDir, fixture string, auditable kubeaudit.Auditable, expectedErrors []string) *kubeaudit.Report { return AuditMultiple(t, fixtureDir, fixture, []kubeaudit.Auditable{auditable}, expectedErrors, "", MANIFEST_MODE) } func AuditLocal(t *testing.T, fixtureDir, fixture string, auditable kubeaudit.Auditable, namespace string, expectedErrors []string) *kubeaudit.Report { return AuditMultiple(t, fixtureDir, fixture, []kubeaudit.Auditable{auditable}, expectedErrors, namespace, LOCAL_MODE) } func AuditMultiple(t *testing.T, fixtureDir, fixture string, auditables []kubeaudit.Auditable, expectedErrors []string, namespace string, mode string) *kubeaudit.Report { if mode == LOCAL_MODE && !UseKind() { return nil } expected := make(map[string]bool, len(expectedErrors)) for _, err := range expectedErrors { expected[err] = true } report := GetReport(t, fixtureDir, fixture, auditables, namespace, mode) require.NotNil(t, report) errors := make(map[string]bool) for _, result := range report.Results() { for _, auditResult := range result.GetAuditResults() { errors[auditResult.Rule] = true } } assert.Equal(t, expected, errors) return report } func FixSetup(t *testing.T, fixtureDir, fixture string, auditable kubeaudit.Auditable) (fixedResources []k8s.Resource, report *kubeaudit.Report) { return FixSetupMultiple(t, fixtureDir, fixture, []kubeaudit.Auditable{auditable}) } // FixSetup runs Fix() on a given manifest and returns the resulting resources func FixSetupMultiple(t *testing.T, fixtureDir, fixture string, auditables []kubeaudit.Auditable) (fixedResources []k8s.Resource, report *kubeaudit.Report) { require := require.New(t) report = GetReport(t, fixtureDir, fixture, auditables, "", MANIFEST_MODE) require.NotNil(report) // This increases code coverage by calling the Plan() method on each PendingFix object. Plan() returns a human // readable description of what Apply() will do and does not need to be otherwise tested for correct logic report.PrintPlan(os.Stdout) // New resources that are created to fix the manifest are not added to the results, only the fixed manifest. By // running the fixed manifest through another audit run, the new resource is treated the same as any other // resource and parsed into a Result fixedManifest := bytes.NewBuffer(nil) err := report.Fix(fixedManifest) require.Nil(err) auditor, err := kubeaudit.New(auditables) require.Nil(err) report, err = auditor.AuditManifest("", fixedManifest) require.Nil(err) results := report.RawResults() fixedResources = make([]k8s.Resource, 0, len(results)) for _, result := range results { resource := result.GetResource().Object() if resource != nil { fixedResources = append(fixedResources, resource) } } return fixedResources, report } func GetReport(t *testing.T, fixtureDir, fixture string, auditables []kubeaudit.Auditable, namespace string, mode string) *kubeaudit.Report { require := require.New(t) fixture = filepath.Join(fixtureDir, fixture) auditor, err := kubeaudit.New(auditables) require.NoError(err) var report *kubeaudit.Report switch mode { case MANIFEST_MODE: manifest, openErr := os.Open(fixture) require.NoError(openErr) defer manifest.Close() report, err = auditor.AuditManifest("", manifest) case LOCAL_MODE: defer DeleteNamespace(t, namespace) CreateNamespace(t, namespace) ApplyManifest(t, fixture, namespace) report, err = auditor.AuditLocal("", "", k8sinternal.ClientOptions{Namespace: namespace}) } require.NoError(err) return report } // GetAllFileNames returns all file names in the given directory // It can be used to retrieve all of the resource manifests from the test/fixtures/all_resources directory // This directory is not hardcoded because the working directory for tests is relative to the test func GetAllFileNames(t *testing.T, directory string) []string { files, err := os.ReadDir(directory) require.Nil(t, err) fileNames := make([]string, 0, len(files)) for _, file := range files { fileNames = append(fileNames, file.Name()) } return fileNames } // UseKind returns true if tests which utilize Kind should run func UseKind() bool { return os.Getenv("USE_KIND") == "true" } func ApplyManifest(t *testing.T, manifestPath, namespace string) { t.Helper() runCmd(t, exec.Command("kubectl", "apply", "-f", manifestPath, "-n", namespace)) } func CreateNamespace(t *testing.T, namespace string) { t.Helper() runCmd(t, exec.Command("kubectl", "create", "namespace", namespace)) } func DeleteNamespace(t *testing.T, namespace string) { t.Helper() runCmd(t, exec.Command("kubectl", "delete", "namespace", namespace)) } func runCmd(t *testing.T, cmd *exec.Cmd) { t.Helper() stdoutStderr, err := cmd.CombinedOutput() fmt.Printf("%s\n", stdoutStderr) require.NoError(t, err) } 0707010000013B000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001F00000000kubeaudit-0.22.2/internal/yaml0707010000013C000081A400000000000000000000000166C635DC00004DBD000000000000000000000000000000000000002700000000kubeaudit-0.22.2/internal/yaml/yaml.gopackage yaml import ( "bytes" "fmt" log "github.com/sirupsen/logrus" goyaml "gopkg.in/yaml.v3" ) // src https://github.com/go-yaml/yaml/blob/v3/resolve.go#L70 const ( strTag = "!!str" seqTag = "!!seq" mapTag = "!!map" ) // Merge merges the original YAML with the fixed YAML such that the resulting YAML is autofixed but with the // same order and comments as the original. func Merge(origData, fixedData []byte) ([]byte, error) { origYaml, err := unmarshal(origData) if err != nil { return nil, err } fixedYaml, err := unmarshal(fixedData) if err != nil { return nil, err } // Create a new document node that contains the merged maps for the original and fixed yaml mergedYaml := shallowCopyNode(origYaml) mergedYaml.Content = []*goyaml.Node{ mergeMaps(origYaml.Content[0], fixedYaml.Content[0]), } return marshal(mergedYaml) } func unmarshal(data []byte) (*goyaml.Node, error) { var node goyaml.Node if err := goyaml.Unmarshal(data, &node); err != nil { return nil, err } if len(node.Content) != 1 { return nil, fmt.Errorf("expected original yaml document to have one child but got %v", len(node.Content)) } if node.Content[0].Kind != goyaml.MappingNode { return nil, fmt.Errorf("expected mapping node as child of original yaml document node but got %v", node.Content[0].Kind) } return &node, nil } func marshal(node *goyaml.Node) ([]byte, error) { data := bytes.NewBuffer(nil) encoder := goyaml.NewEncoder(data) defer encoder.Close() encoder.SetIndent(2) if err := encoder.Encode(node); err != nil { return nil, fmt.Errorf("error marshaling merged yaml: %v", err) } return data.Bytes(), nil } // mergeMaps recursively merges orig and fixed. // Key-value pairs which exist in orig but not fixed are excluded (as determined by matching the key). // Key-value pairs which exist in fixed but not orig are included. // If keys exist in both orig and fixed then the key-value pair from fixed is used unless both values are complex // (maps or sequences), in which case they are merged recursively. func mergeMaps(orig, fixed *goyaml.Node) *goyaml.Node { merged := shallowCopyNode(orig) origContent := orig.Content fixedContent := fixed.Content // Drop items from original if they are not in fixed for i := 0; i < len(origContent); i += 2 { origKey := origContent[i] if isKeyInMap(origKey, fixed) { origVal := origContent[i+1] merged.Content = append(merged.Content, origKey, origVal) } } // Update or add items from the fixed yaml which are not in the original for i := 0; i < len(fixedContent); i += 2 { fixedKey := fixedContent[i] fixedVal := fixedContent[i+1] if mergedKeyIndex := findKeyInMap(fixedKey, merged); mergedKeyIndex == -1 { // Add item merged.Content = append(merged.Content, fixedKey, fixedVal) } else { // Update item mergedValIndex := mergedKeyIndex + 1 mergedVal := merged.Content[mergedValIndex] if fixedVal.Kind != mergedVal.Kind { merged.Content[mergedValIndex] = fixedVal continue } switch fixedVal.Kind { case goyaml.ScalarNode: merged.Content[mergedValIndex].Value = fixedVal.Value case goyaml.MappingNode: merged.Content[mergedValIndex] = mergeMaps(mergedVal, fixedVal) case goyaml.SequenceNode: merged.Content[mergedValIndex] = mergeSequences(fixedKey.Value, mergedVal, fixedVal) default: log.Error("Unexpected yaml node kind", fixedVal.Kind) } } } return merged } // mergeSequences recursively merges orig and fixed. // Items which exist in orig but not fixed are excluded. // Items which exist in fixed but not orig are included. // If items exist in both orig and fixed then the item from fixed is used unless both items are complex // (maps or sequences), in which case they are merged recursively. func mergeSequences(sequenceKey string, orig, fixed *goyaml.Node) *goyaml.Node { merged := shallowCopyNode(orig) origContent := orig.Content fixedContent := fixed.Content // Drop items from original if they are not in fixed for _, origItem := range origContent { if isItemInSequence(sequenceKey, origItem, fixed) { merged.Content = append(merged.Content, origItem) } } // Update or add items from the fixed yaml which are not in the original for _, fixedItem := range fixedContent { if mergedItemIndex := findItemInSequence(sequenceKey, fixedItem, merged); mergedItemIndex == -1 { // Add item merged.Content = append(merged.Content, fixedItem) } else { // Update item mergedItem := merged.Content[mergedItemIndex] switch { case fixedItem.Kind != mergedItem.Kind: merged.Content[mergedItemIndex] = fixedItem case fixedItem.Kind == goyaml.MappingNode: merged.Content[mergedItemIndex] = mergeMaps(mergedItem, fixedItem) case fixedItem.Kind == goyaml.SequenceNode: merged.Content[mergedItemIndex] = mergeSequences(sequenceKey, mergedItem, fixedItem) } } } return merged } // deepEqual recursively compares two values but ignores map and array child order and comments. For example the // following values are considered to be equal: // // []goyaml.SequenceItem{{Value: goyaml.MapSlice{ // {Key: "k", Value: "v", Comment: "c"}, // {Key: "k2", Value: "v2", Comment: "c2"}, // }}} // // []goyaml.SequenceItem{{Value: goyaml.MapSlice{ // {Key: "k2", Value: "v2"}, // {Key: "k", Value: "v"}, // }}} func deepEqual(val1, val2 *goyaml.Node) bool { if val1.Kind != val2.Kind { return false } switch val1.Kind { case goyaml.ScalarNode: return equalScalar(val1, val2) case goyaml.MappingNode: return equalMap(val1, val2) case goyaml.SequenceNode: return equalSequence(val1, val2) } return false } func equalScalar(val1, val2 *goyaml.Node) bool { if val1.Kind != goyaml.ScalarNode || val2.Kind != goyaml.ScalarNode { return false } return val1.Tag == val2.Tag && val1.Value == val2.Value } func equalSequence(seq1, seq2 *goyaml.Node) bool { if seq1.Kind != goyaml.SequenceNode || seq2.Kind != goyaml.SequenceNode { return false } content1 := seq1.Content content2 := seq2.Content if len(content1) != len(content2) { return false } for _, val1 := range content1 { if !isItemInSequence("", val1, seq2) { return false } } return true } func equalMap(map1, map2 *goyaml.Node) bool { if map1.Kind != goyaml.MappingNode || map2.Kind != goyaml.MappingNode { return false } content1 := map1.Content content2 := map2.Content if len(content1) != len(content2) { return false } for i := 0; i < len(content1); i += 2 { key1 := content1[i] index2 := findKeyInMap(key1, map2) if index2 == -1 { return false } value1 := content1[i+1] value2 := content2[index2+1] if !deepEqual(value1, value2) { return false } } return true } // equalValueForKey returns true if map1 and map2 have the same key-value pair for the given key func equalValueForKey(findKey string, map1, map2 *goyaml.Node) bool { if map1.Kind != goyaml.MappingNode || map2.Kind != goyaml.MappingNode { return false } if val1, index1 := findValInMap(findKey, map1); index1 != -1 { if val2, index2 := findValInMap(findKey, map2); index2 != -1 { return deepEqual(val1, val2) } } return false } // isKeyInMap returns true if findKey is a child of mapNode func isKeyInMap(findKey *goyaml.Node, mapNode *goyaml.Node) bool { return findKeyInMap(findKey, mapNode) != -1 } // findKeyInMap returns the index of findKey in mapNode's list of children, or -1 if it isn't found func findKeyInMap(findKey *goyaml.Node, mapNode *goyaml.Node) int { if mapNode.Kind != goyaml.MappingNode { return -1 } children := mapNode.Content for i := 0; i < len(children); i += 2 { key := children[i] if deepEqual(key, findKey) { return i } } return -1 } // findValInMap returns the child of mapNode which is value corresponding to the given key, and its index func findValInMap(key string, mapNode *goyaml.Node) (*goyaml.Node, int) { findKey := &goyaml.Node{ Kind: goyaml.ScalarNode, Value: key, Tag: strTag, } keyIndex := findKeyInMap(findKey, mapNode) if keyIndex == -1 { return nil, -1 } valIndex := keyIndex + 1 return mapNode.Content[valIndex], valIndex } // isItemInSequence returns true if findVal is a child of sequenceNode func isItemInSequence(sequenceKey string, findVal *goyaml.Node, sequenceNode *goyaml.Node) bool { return findItemInSequence(sequenceKey, findVal, sequenceNode) != -1 } // findItemInSequence returns the index of the child in sequenceNode which "matches" findVal. See sequenceItemMatch for // what is considered a "match". Returns -1 if there is no match found. func findItemInSequence(sequenceKey string, findVal *goyaml.Node, sequenceNode *goyaml.Node) int { children := sequenceNode.Content for i, val := range children { if sequenceItemMatch(sequenceKey, val, findVal) { return i } } return -1 } var identifyingKey = map[string]string{ "allowedFlexVolumes": "driver", // PodSecurityPolicySpec.allowedFlexVolumes : AllowedFlexVolume.driver "allowedHostPaths": "pathPrefix", // PodSecurityPolicySpec.allowedHostPaths : AllowedHostPath.pathPrefix // StorageClass.allowedTopologies : TopologySelectorTerm.matchLabelExpressions "allowedTopologies": "matchLabelExpressions", "clusterRoleSelectors": "matchExpressions", // AggregationRule.clusterRoleSelectors : LabelSelector.matchExpressions "containers": "name", // PodSpec.containers : Container.name "egress": "ports", // NetworkPolicySpec.egress : NetworkPolicyEgressRule.ports "env": "name", // Container.env : EnvVar.name "hostAliases": "ip", // PodSpec.hostAliases : HostAlias.ip // Assumes it is not possible to add multiple values for the same header, ie. // httpHeaders: // - name: header1 // value: value1 // - name: header1 // value: value2 // This restriction is not documented so the assumption may be incorrect // See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#httpheader-v1-core "httpHeaders": "name", // HTTPGetAction.httpHeaders : HTTPHeader.name // PodSpec.imagePullSecrets : LocalObjectReference.name // ServiceAccount.imagePullSecrets : LocalObjectReference.name "imagePullSecrets": "name", "initContainers": "name", // PodSpec.initContainers : Container.name // LabelSelector.matchExpressions : LabelSelectorRequirement.key // NodeSelectorTerm.matchExpressions : NodeSelectorRequirement.key "matchExpressions": "key", "matchFields": "key", // NodeSelectorTerm.matchFields : NodeSelectorRequirement.key "options": "name", // PodDNSConfig.options : PodDNSConfigOption.name // TopologySelectorTerm.matchLabelExpressions : TopologySelectorLabelRequirement.key "matchLabelExpressions": "key", "pending": "name", // Initializers.pending : Initializer.name "readinessGates": "conditionType", // PodSpec.readinessGates : PodReadinessGate.conditionType // PodAffinity.requiredDuringSchedulingIgnoredDuringExecution : PodAffinityTerm.labelSelector // PodAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution : PodAffinityTerm.labelSelector "requiredDuringSchedulingIgnoredDuringExecution": "labelSelector", "secrets": "name", // ServiceAccount.secrets : ObjectReference.name // ClusterRoleBinding.subjects : Subject.name // RoleBinding.subjects : Subject.name "subjects": "name", "subsets": "addresses", // Endpoints.subsets : EndpointSubset.addresses "sysctls": "name", // PodSecurityContext.sysctls : Sysctl.name "taints": "key", // NodeSpec.taints : Taint.key "volumeDevices": "devicePath", // Container.volumeDevices : VolumeDevice.devicePath "volumeMounts": "mountPath", // Container.volumeMounts : VolumeMount.mountPath "volumes": "name", // PodSpec.volumes : Volume.name } // sequenceItemMatch returns true if item1 and item2 are a match, false otherwise. In order to determine whether // sequence items match (and should be merged) we determine the "identifying key" for the sequence item, and if both // sequence items have the same key-value pair for the "identifying key" then they are a match. The sequenceKey // is the key for which the array items are the value. ie: // sequenceKey: // - item1 // - item2 func sequenceItemMatch(sequenceKey string, item1, item2 *goyaml.Node) bool { if item1.Kind != item2.Kind { return false } if sequenceKey == "" || item1.Kind != goyaml.MappingNode { return deepEqual(item1, item2) } if idKey, ok := identifyingKey[sequenceKey]; ok { return equalValueForKey(idKey, item1, item2) } switch sequenceKey { // EndpointSubset.addresses : EndpointAddress.[hostname OR ip] // EndpointSubset.notReadyAddresses : EndpointAddress.[hostname OR ip] case "addresses", "notReadyAddresses": if equalValueForKey("hostname", item1, item2) { return true } return equalValueForKey("ip", item1, item2) // Container.envFrom : EnvFromSource.[configMapRef OR secretRef].name case "envFrom": if val1, index1 := findValInMap("configMapRef", item1); index1 != -1 { if val2, index2 := findValInMap("configMapRef", item2); index2 != -1 { return equalValueForKey("name", val1, val2) } } if val1, index1 := findValInMap("secretRef", item1); index1 != -1 { if val2, index2 := findValInMap("secretRef", item2); index2 != -1 { return equalValueForKey("name", val1, val2) } } return false // NetworkPolicySpec.ingress : NetworkPolicyIngressRule.[ports OR from] case "ingress": if equalValueForKey("ports", item1, item2) { return true } return equalValueForKey("from", item1, item2) // ConfigMapProjection.items : KeyToPath.key // ConfigMapVolumeSource.items : KeyToPath.key // DownwardAPIVolumeSource.items : DownwardAPIVolumeFile.path // SecretSecretProjection.items : KeyToPath.key // SecretVolumeSource.items : KeyToPath.key case "items": // ConfigMapVolumeSource.items : KeyToPath.key // SecretVolumeSource.items : KeyToPath.key if equalValueForKey("key", item1, item2) { return true } // DownwardAPIVolumeSource.items : DownwardAPIVolumeFile.path return equalValueForKey("path", item1, item2) // NodeSelector.nodeSelectorTerms : NodeSelectorTerm.[matchExpressions OR matchFields] case "nodeSelectorTerms": // This is a bit of a complicated case. // See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#nodeselector-v1-core // For now, only match if there is an exact match for the complex value of either the "matchExpressions" or // "matchFields" fields. if equalValueForKey("matchExpressions", item1, item2) { return true } return equalValueForKey("matchFields", item1, item2) // ObjectMeta.ownerReferences : OwnerReference.[uid OR name] case "ownerReferences": if equalValueForKey("uid", item1, item2) { return true } return equalValueForKey("name", item1, item2) // NodeAffinity.preferredDuringSchedulingIgnoredDuringExecution : PreferredSchedulingTerm.preference // PodAffinity.preferredDuringSchedulingIgnoredDuringExecution : WeightedPodAffinityTerm.podAffinityTerm // PodAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution : WeightedPodAffinityTerm.podAffinityTerm case "preferredDuringSchedulingIgnoredDuringExecution": // This is a bit of a complicated case as the values are very nested and because the same identifying key is // used for two different array types (PreferredSchedulingTerm and WeightedPodAffinityTerm). // See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#nodeaffinity-v1-core // and https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#podaffinity-v1-core // For now, only match if there is an exact match for the complex value of the "preference" or // "podAffinityTerm" field. // The value for the "weight" field can be updated. // NodeAffinity.preferredDuringSchedulingIgnoredDuringExecution : PreferredSchedulingTerm.preference if equalValueForKey("preference", item1, item2) { return true } // PodAffinity.preferredDuringSchedulingIgnoredDuringExecution : WeightedPodAffinityTerm.podAffinityTerm // PodAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution : WeightedPodAffinityTerm.podAffinityTerm return equalValueForKey("podAffinityTerm", item1, item2) // Container.ports : ContainerPort.containerPort // EndpointSubset.ports : EndpointPort.port // ServiceSpec.ports : ServicePort.port case "ports": // Container.ports : ContainerPort.containerPort if equalValueForKey("containerPort", item1, item2) { return true } // EndpointSubset.ports : EndpointPort.port // ServiceSpec.ports : ServicePort.port return equalValueForKey("port", item1, item2) // ClusterRole.rules : PolicyRule.resources // IngressSpec.rules : IngressRule.host // Role.rules : PolicyRule.resources case "rules": // ClusterRole.rules : PolicyRule.resources // Role.rules : PolicyRule.resources if equalValueForKey("resources", item1, item2) { return true } // IngressSpec.rules : IngressRule.host if equalValueForKey("host", item1, item2) { return true } return deepEqual(item1, item2) // ProjectedVolumeSource.sources case "sources": // ProjectedVolumeSource.sources : VolumeProjection.configMap.name if val1, index1 := findValInMap("configMap", item1); index1 != -1 { if val2, index2 := findValInMap("configMap", item2); index2 != -1 { return equalValueForKey("name", val1, val2) } return false } // ProjectedVolumeSource.sources : VolumeProjection.downwardAPI.items if val1, index1 := findValInMap("downwardAPI", item1); index1 != -1 { if val2, index2 := findValInMap("downwardAPI", item2); index2 != -1 { return equalValueForKey("items", val1, val2) } return false } // ProjectedVolumeSource.sources : VolumeProjection.secret.name if val1, index1 := findValInMap("secret", item1); index1 != -1 { if val2, index2 := findValInMap("secret", item2); index2 != -1 { return equalValueForKey("name", val1, val2) } return false } // ProjectedVolumeSource.sources : VolumeProjection.serviceAccountToken.path if val1, index1 := findValInMap("serviceAccountToken", item1); index1 != -1 { if val2, index2 := findValInMap("serviceAccountToken", item2); index2 != -1 { return equalValueForKey("path", val1, val2) } } return false // IngressSpec.tls : IngressTLS.[secretName OR hosts] case "tls": if equalValueForKey("secretName", item1, item2) { return true } return equalValueForKey("hosts", item1, item2) // StatefulSetSpec.volumeClaimTemplates : PersistentVolumeClaim.metadata.name case "volumeClaimTemplates": if val1, index1 := findValInMap("metadata", item1); index1 != -1 { if val2, index2 := findValInMap("metadata", item2); index2 != -1 { return equalValueForKey("name", val1, val2) } } return false } // FSGroupStrategyOptions.ranges : IDRange // RunAsGroupStrategyOptions.ranges : IDRange // RunAsUserStrategyOptions.ranges : IDRange // SupplementalGroupsStrategyOptions.ranges : IDRange // PodSecurityPolicySpec.hostPorts : HostPortRange // PodSpec.tolerations : Toleration return deepEqual(item1, item2) } // Returns a new *goyaml.Node with the same values as the original node except for the Content field, which is initialized // to an empty array func shallowCopyNode(orig *goyaml.Node) *goyaml.Node { return &goyaml.Node{ Kind: orig.Kind, Style: orig.Style, Tag: orig.Tag, Value: orig.Value, Anchor: orig.Anchor, Alias: orig.Alias, Content: []*goyaml.Node{}, HeadComment: orig.HeadComment, LineComment: orig.LineComment, FootComment: orig.FootComment, Line: orig.Line, Column: orig.Column, } } 0707010000013D000081A400000000000000000000000166C635DC00007C47000000000000000000000000000000000000002C00000000kubeaudit-0.22.2/internal/yaml/yaml_test.gopackage yaml import ( "testing" "github.com/stretchr/testify/assert" yaml "gopkg.in/yaml.v3" ) func TestMerge(t *testing.T) { assert := assert.New(t) // empty yaml _, err := Merge(nil, nil) assert.NotNil(err) _, err = Merge([]byte{}, []byte{}) assert.NotNil(err) _, err = Merge([]byte(""), []byte("")) assert.NotNil(err) // invalid yaml _, err = Merge([]byte("a: b: c"), nil) assert.NotNil(err) _, err = Merge([]byte("a:"), nil) assert.NotNil(err) _, err = Merge([]byte("a: b"), []byte("a: b: c")) assert.NotNil(err) // non-map root node _, err = Merge([]byte("- a"), nil) assert.NotNil(err) _, err = Merge([]byte("a: b"), []byte("- a")) assert.NotNil(err) // valid yaml merged, err := Merge([]byte("a: b"), []byte("a: b")) assert.NoError(err) assert.Equal("a: b\n", string(merged)) } func TestMergeMaps(t *testing.T) { cases := []struct { testName string orig *yaml.Node fixed *yaml.Node merged *yaml.Node }{ { "Update", &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "K", HeadComment: "Hi"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "V", LineComment: "Bye"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "K"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "V2"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "K", HeadComment: "Hi"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "V2", LineComment: "Bye"}, }}, }, { "Add", &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", LineComment: "Comment"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", LineComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, }}, }, { "Remove", &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", LineComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "Comment 2"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "Comment 2"}, }}, }, { "Preserve order", &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k", HeadComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", LineComment: "EOL Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2", HeadComment: "Comment 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "EOL Comment 2", FootComment: "Foot Comment"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "knew"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "vnew"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2new"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "vnew"}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k", HeadComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "vnew", LineComment: "EOL Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2", HeadComment: "Comment 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2new", LineComment: "EOL Comment 2", FootComment: "Foot Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "knew"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "vnew"}, }}, }, { "Sequence of strings", &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "values"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "v1", LineComment: "EOL Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", HeadComment: "Comment 1", LineComment: "EOL Comment 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v3", HeadComment: "EOL Comment 3"}, }}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "values"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "v3"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v4"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v1"}, }}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "values"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "v1", LineComment: "EOL Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v3", HeadComment: "EOL Comment 3"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v4"}, }}, }}, }, { "Map of maps", &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "a"}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "b", LineComment: "EOL Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "c", HeadComment: "Comment 1", LineComment: "EOL Comment 2"}, }}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "a"}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "b"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "d"}, }}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "a"}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "b", LineComment: "EOL Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "d", HeadComment: "Comment 1", LineComment: "EOL Comment 2"}, }}, }}, }, { "Complex mapslice (nested sequence and mapslices)", &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "containers"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "container1", LineComment: "Container 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "image"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "image1", LineComment: "Image 1"}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "container2", LineComment: "Container 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ports"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "protocol"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "TCP"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "containerPort"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "3000", LineComment: "Port 3000"}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "protocol", HeadComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "UDP"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "containerPort"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "6000", LineComment: "Port 6000"}, }}, }}, }}, }}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "subsets"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "addresses"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "hostname"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "hname", LineComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ip"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "10.0.0.1"}, }}, }}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ports"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "pname", LineComment: "Comment 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "3000"}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "pname2", LineComment: "Comment 3"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "8000"}, }}, }}, }}, }}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "containers"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "container3"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "image"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "image1"}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "container2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ports"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "containerPort"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "6000"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "protocol"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "TCP"}, }}, }}, }}, }}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "subsets"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "addresses"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "hostname"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "hname"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ip"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "10.0.0.1"}, }}, }}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ports"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "pname"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "6000"}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "newname"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "8000"}, }}, }}, }}, }}, }}, &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "containers"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "container2", LineComment: "Container 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ports"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "protocol", HeadComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "TCP"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "containerPort"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "6000", LineComment: "Port 6000"}, }}, }}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "container3"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "image"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "image1"}, }}, }}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "subsets"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "addresses"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "hostname"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "hname", LineComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ip"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "10.0.0.1"}, }}, }}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "ports"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "newname", LineComment: "Comment 3"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "8000"}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "pname"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "6000"}, }}, }}, }}, }}, }}, }, } for _, test := range cases { t.Run(test.testName, func(t *testing.T) { merged := mergeMaps(test.orig, test.fixed) assert.True(t, deepEqual(test.merged, merged)) assert.Equal(t, test.merged, merged) }) } } func TestFindItemInSequence(t *testing.T) { assert := assert.New(t) // same string seq := &yaml.Node{Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", HeadComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "Comment 2"}, }} item := &yaml.Node{Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"} index := findItemInSequence("", item, seq) assert.Equal(1, index) // different string seq = &yaml.Node{Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", HeadComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "Comment 2"}, }} item = &yaml.Node{Kind: yaml.ScalarNode, Tag: strTag, Value: "v3"} index = findItemInSequence("", item, seq) assert.Equal(-1, index) // matching mapslice seq = &yaml.Node{Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "protocol"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "TCP"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "containerPort"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "3000"}, }}, {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "protocol"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "UDP", HeadComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "containerPort"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "6000", LineComment: "Comment 2"}, }}, }} item = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "name"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "port1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "protocol"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "TCP"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "containerPort"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "6000"}, }} index = findItemInSequence("ports", item, seq) assert.Equal(1, index) } func TestFindItemInMapSlice(t *testing.T) { assert := assert.New(t) // key present m := &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k", HeadComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", LineComment: "Comment 2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, }} item, index := findValInMap("k2", m) assert.Equal(3, index) assert.True(deepEqual(m.Content[3], item)) // key not present m = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k", HeadComment: "Comment"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", LineComment: "Comment 2"}, }} _, index = findValInMap("k2", m) assert.Equal(-1, index) } func TestEqualValueForKey(t *testing.T) { assert := assert.New(t) // same string m1 := &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "Comment 2"}, }} m2 := &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "k2"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, }} assert.True(equalValueForKey("k2", m1, m2)) // different string m1 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v"}, }} m2 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, }} assert.False(equalValueForKey("k2", m1, m2)) // string and number m1 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "2"}, }} m2 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: "!!int", Value: "2"}, }} assert.False(equalValueForKey("k", m1, m2)) } func TestSequenceItemMatch(t *testing.T) { cases := []struct { testName string item1 *yaml.Node item2 *yaml.Node sequenceKey string expected bool }{ { testName: "Same strings", item1: &yaml.Node{Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "Comment"}, item2: &yaml.Node{Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, sequenceKey: "", expected: true, }, { testName: "Different strings", item1: &yaml.Node{Kind: yaml.ScalarNode, Tag: strTag, Value: "v", LineComment: "Comment"}, item2: &yaml.Node{Kind: yaml.ScalarNode, Tag: strTag, Value: "v2", LineComment: "Comment"}, sequenceKey: "", expected: false, }, { testName: "String and mapslice", item1: &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, LineComment: "Comment"}, }, LineComment: "Comment"}, item2: &yaml.Node{Kind: yaml.ScalarNode, Tag: strTag, Value: "v2"}, sequenceKey: "", expected: false, }, } for _, test := range cases { t.Run(test.testName, func(t *testing.T) { assert.Equal(t, test.expected, sequenceItemMatch(test.sequenceKey, test.item1, test.item2)) }) } cases2 := []struct { testName string sequenceKey string mapKey string }{ {"EndpointSubset.addresses : EndpointAddress.hostname", "addresses", "hostname"}, {"EndpointSubset.addresses : EndpointAddress.ip", "addresses", "ip"}, {"EndpointSubset.notReadyAddresses : EndpointAddress.hostname", "notReadyAddresses", "hostname"}, {"EndpointSubset.notReadyAddresses : EndpointAddress.ip", "notReadyAddresses", "ip"}, {"NetworkPolicySpec.ingress : NetworkPolicyIngressRule.ports", "ingress", "ports"}, {"NetworkPolicySpec.ingress : NetworkPolicyIngressRule.from", "ingress", "from"}, {"ConfigMapProjection.items : KeyToPath.key", "items", "key"}, {"DownwardAPIVolumeSource.items : DownwardAPIVolumeFile.path", "items", "path"}, {"NodeSelector.nodeSelectorTerms : NodeSelectorTerm.matchExpressions", "nodeSelectorTerms", "matchExpressions"}, {"NodeSelector.nodeSelectorTerms : NodeSelectorTerm.matchFields", "nodeSelectorTerms", "matchFields"}, {"ObjectMeta.ownerReferences : OwnerReference.uid", "ownerReferences", "uid"}, {"ObjectMeta.ownerReferences : OwnerReference.name", "ownerReferences", "name"}, {"NodeAffinity.preferredDuringSchedulingIgnoredDuringExecution : PreferredSchedulingTerm.preference", "preferredDuringSchedulingIgnoredDuringExecution", "preference"}, {"PodAffinity.preferredDuringSchedulingIgnoredDuringExecution : WeightedPodAffinityTerm.podAffinityTerm", "preferredDuringSchedulingIgnoredDuringExecution", "podAffinityTerm"}, {"ClusterRole.rules : PolicyRule.resources", "rules", "resources"}, {"IngressSpec.rules : IngressRule.host", "rules", "host"}, {"IngressSpec.rules : IngressRule.host", "rules", "host"}, {"IngressSpec.tls : IngressTLS.secretName", "tls", "secretName"}, {"IngressSpec.tls : IngressTLS.hosts", "tls", "hosts"}, } for _, test := range cases2 { t.Run(test.testName, func(t *testing.T) { item1 := &yaml.Node{Kind: yaml.MappingNode, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: test.mapKey}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "value"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "randkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "randval"}, }} item2 := &yaml.Node{Kind: yaml.MappingNode, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: test.mapKey}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "value"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "randkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "otherval"}, }} assert.True(t, sequenceItemMatch(test.sequenceKey, item1, item2)) item2.Content[1].Value = "othervalue" assert.False(t, sequenceItemMatch(test.sequenceKey, item1, item2)) }) } // nested maps cases3 := []struct { testName string sequenceKey string intermediateMapKey string mapKey string }{ {"ProjectedVolumeSource.sources : VolumeProjection.configMap.name", "sources", "configMap", "name"}, {"ProjectedVolumeSource.sources : VolumeProjection.downwardAPI.items", "sources", "downwardAPI", "items"}, {"ProjectedVolumeSource.sources : VolumeProjection.secret.name", "sources", "secret", "name"}, {"ProjectedVolumeSource.sources : VolumeProjection.serviceAccountToken.name", "sources", "serviceAccountToken", "path"}, {"Container.envFrom : EnvFromSource.configMapRef.name", "envFrom", "configMapRef", "name"}, {"Container.envFrom : EnvFromSource.secretRef.name", "envFrom", "secretRef", "name"}, } for _, test := range cases3 { t.Run(test.testName, func(t *testing.T) { item1 := &yaml.Node{Kind: yaml.MappingNode, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: test.intermediateMapKey}, {Kind: yaml.MappingNode, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: test.mapKey}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "value"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "randkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "randval"}, }}, }} item2 := &yaml.Node{Kind: yaml.MappingNode, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: test.intermediateMapKey}, {Kind: yaml.MappingNode, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: test.mapKey}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "value"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "randkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "otherval"}, }}, }} assert.True(t, sequenceItemMatch(test.sequenceKey, item1, item2)) item2.Content[1].Content[1].Value = "othervalue" assert.False(t, sequenceItemMatch(test.sequenceKey, item1, item2)) item2.Content[0].Value = "bla" assert.False(t, sequenceItemMatch(test.sequenceKey, item1, item2)) }) } } func TestDeepEqual(t *testing.T) { assert := assert.New(t) var v1 *yaml.Node var v2 *yaml.Node // maps should be equal, regardless of comments v1 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k", HeadComment: "head", LineComment: "line", FootComment: "foot"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", HeadComment: "head", LineComment: "line", FootComment: "foot"}, }} v2 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "k"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "v"}, }} assert.True(deepEqual(v1, v2)) // sequences should be equal, regardless of comments v1 = &yaml.Node{Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "v", HeadComment: "head", LineComment: "line", FootComment: "foot"}, }} v2 = &yaml.Node{Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "v"}, }} assert.True(deepEqual(v1, v2)) v1 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "matchExpressions"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "key"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "labelkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "operator"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "In"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "values"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "value1", LineComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "value2"}, }}, }}, }}, }} v2 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "matchExpressions"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "key"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "labelkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "values"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "value1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "value2"}, }}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "operator"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "In"}, }}, }}, }} assert.True(deepEqual(v1, v2)) v1 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "matchExpressions"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "key"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "labelkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "operator"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "In"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "values"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "value1", LineComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "value2"}, }}, }}, }}, }} v2 = &yaml.Node{Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "matchExpressions"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.MappingNode, Tag: mapTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "key"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "labelkey"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "operator"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "In"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "values"}, {Kind: yaml.SequenceNode, Tag: seqTag, Content: []*yaml.Node{ {Kind: yaml.ScalarNode, Tag: strTag, Value: "value1", LineComment: "Comment 1"}, {Kind: yaml.ScalarNode, Tag: strTag, Value: "newvalue"}, }}, }}, }}, }} assert.False(deepEqual(v1, v2)) } 0707010000013E000081A400000000000000000000000166C635DC000003C5000000000000000000000000000000000000001900000000kubeaudit-0.22.2/kube.gopackage kubeaudit import "github.com/Shopify/kubeaudit/pkg/k8s" // ErrorUnsupportedResource occurs when Kubeaudit doesn't know how to audit the resource const ErrorUnsupportedResource = "Unsupported resource" // RedundantAuditorOverride is the audit result name given when an override label is used to disable an auditor, // but that auditor found no security issues so the label is redundant const RedundantAuditorOverride = "RedundantAuditorOverride" // KubeResource is a wrapper around a Kubernetes object type KubeResource interface { // Object is a pointer to a Kubernetes resource. The resource may be modified by multiple auditors Object() k8s.Resource // Bytes is the original byte representation of the resource Bytes() []byte } // Implements KubeResource type kubeResource struct { object k8s.Resource bytes []byte } func (k *kubeResource) Object() k8s.Resource { return k.object } func (k *kubeResource) Bytes() []byte { return k.bytes } 0707010000013F000081A400000000000000000000000166C635DC00002262000000000000000000000000000000000000001E00000000kubeaudit-0.22.2/kubeaudit.go// Package kubeaudit provides methods to find and fix security issues in Kubernetes resources. // // # Modes // // Kubeaudit supports three different modes. The mode used depends on the audit method used. // // 1. Manifest mode: Audit a manifest file // // 2. Local mode: Audit resources in a local kubeconfig file // // 3. Cluster mode: Audit resources in a running cluster (kubeaudit must be invoked from a container within the cluster) // // In manifest mode, kubeaudit can automatically fix security issues. // // Follow the instructions below to use kubeaudit: // // # First initialize the security auditors // // The auditors determine which security issues kubeaudit will look for. Each auditor is responsible for a different // security issue. For an explanation of what each auditor checks for, see https://github.com/Shopify/kubeaudit#auditors. // // To initialize all available auditors: // // import "github.com/Shopify/kubeaudit/auditors/all" // // auditors, err := all.Auditors(config.KubeauditConfig{}) // // Or, to initialize specific auditors, import each one: // // import ( // "github.com/Shopify/kubeaudit/auditors/apparmor" // "github.com/Shopify/kubeaudit/auditors/image" // ) // // auditors := []kubeaudit.Auditable{ // apparmor.New(), // image.New(image.Config{Image: "myimage:mytag"}), // } // // # Initialize Kubeaudit // // Create a new instance of kubeaudit: // // kubeAuditor, err := kubeaudit.New(auditors) // // # Run the audit // // To run the audit in manifest mode: // // import "os" // // manifest, err := os.Open("/path/to/manifest.yaml") // if err != nil { // ... // } // // report, err := kubeAuditor.AuditManifest(manifest) // // Or, to run the audit in local mode: // // report, err := kubeAuditor.AuditLocal("/path/to/kubeconfig.yml", kubeaudit.AuditOptions{}) // // Or, to run the audit in cluster mode (pass it a namespace name as a string to only audit resources in that namespace, or an empty string to audit resources in all namespaces): // // report, err := auditor.AuditCluster(kubeaudit.AuditOptions{}) // // # Get the results // // To print the results in a human readable way: // // report.PrintResults() // // Results are printed to standard out by default. To print to a string instead: // // var buf bytes.Buffer // report.PrintResults(kubeaudit.WithWriter(&buf), kubeaudit.WithColor(false)) // resultsString := buf.String() // // Or, to get the result objects: // // results := report.Results() // // # Autofix // // Note that autofixing is only supported in manifest mode. // // To print the plan (what will be fixed): // // report.PrintPlan(os.Stdout) // // To automatically fix the security issues and print the fixed manifest: // // err = report.Fix(os.Stdout) // // # Override Errors // // Overrides can be used to ignore specific auditors for specific containers or pods. // See the documentation for the specific auditor you wish to override at https://github.com/Shopify/kubeaudit#auditors. // // # Custom Auditors // // Kubeaudit supports custom auditors. See the Custom Auditor example. package kubeaudit import ( "errors" "fmt" "io" "path/filepath" "strings" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/pkg/k8s" ) // Kubeaudit provides functions to audit and fix Kubernetes manifests type Kubeaudit struct { auditors []Auditable } type AuditOptions = k8sinternal.ClientOptions // New returns a new Kubeaudit instance func New(auditors []Auditable, opts ...Option) (*Kubeaudit, error) { if len(auditors) == 0 { return nil, errors.New("no auditors enabled") } auditor := &Kubeaudit{ auditors: auditors, } if err := auditor.parseOptions(opts); err != nil { return nil, err } return auditor, nil } // AuditManifest audits the Kubernetes resources in the provided manifest func (a *Kubeaudit) AuditManifest(manifestPath string, manifest io.Reader) (*Report, error) { manifestBytes, err := io.ReadAll(manifest) if err != nil { return nil, err } resources, err := getResourcesFromManifest(manifestBytes) if err != nil { return nil, fmt.Errorf("failed to get resources from manifest: %w", err) } results, err := auditResources(resources, a.auditors) if err != nil { return nil, err } for _, result := range results { auditResults := result.GetAuditResults() if !filepath.IsAbs(manifestPath) { manifestPath = strings.TrimPrefix(filepath.Clean("/"+manifestPath), "/") } for _, ar := range auditResults { ar.FilePath = manifestPath } } report := NewReport(results) return report, nil } // AuditCluster audits the Kubernetes resources found in the cluster in which Kubeaudit is running func (a *Kubeaudit) AuditCluster(options AuditOptions) (*Report, error) { if !k8sinternal.IsRunningInCluster(k8sinternal.DefaultClient) { return nil, errors.New("failed to audit resources in cluster mode: not running in cluster") } client, err := k8sinternal.NewKubeClientCluster(k8sinternal.DefaultClient) if err != nil { return nil, err } resources, err := getResourcesFromClient(client, options) if err != nil { return nil, err } results, err := auditResources(resources, a.auditors) if err != nil { return nil, err } report := NewReport(results) return report, nil } // AuditLocal audits the Kubernetes resources found in the provided Kubernetes config file func (a *Kubeaudit) AuditLocal(configpath string, context string, options AuditOptions) (*Report, error) { client, err := k8sinternal.NewKubeClientLocal(configpath, context) if err == k8sinternal.ErrNoReadableKubeConfig { return nil, fmt.Errorf("failed to open kubeconfig file %s", configpath) } else if err != nil { return nil, err } resources, err := getResourcesFromClient(client, options) if err != nil { return nil, err } results, err := auditResources(resources, a.auditors) if err != nil { return nil, err } report := NewReport(results) return report, nil } // Report contains the results after auditing type Report struct { results []Result } func NewReport(results []Result) *Report { return &Report{results} } // RawResults returns all of the results for each Kubernetes resource, including ones that had no audit results. // Generally, you will want to use Results() instead. func (r *Report) RawResults() []Result { return r.results } // Results returns the audit results for each Kubernetes resource func (r *Report) Results() []Result { results := make([]Result, 0, len(r.RawResults())) for _, result := range r.RawResults() { if len(result.GetAuditResults()) > 0 { results = append(results, result) } } return results } // ResultsWithMinSeverity returns the audit results for each Kubernetes resource with a minimum severity func (r *Report) ResultsWithMinSeverity(minSeverity SeverityLevel) []Result { var results []Result for _, result := range r.RawResults() { var filteredAuditResults []*AuditResult for _, auditResult := range result.GetAuditResults() { if auditResult.Severity >= minSeverity { filteredAuditResults = append(filteredAuditResults, auditResult) } } if len(filteredAuditResults) > 0 { results = append(results, &WorkloadResult{ Resource: result.GetResource(), AuditResults: filteredAuditResults, }) } } return results } // HasErrors returns true if any findings have the level of Error func (r *Report) HasErrors() (errorsFound bool) { for _, workloadResult := range r.Results() { for _, auditResult := range workloadResult.GetAuditResults() { if auditResult.Severity >= Error { return true } } } return false } // PrintResults writes the audit results to the specified writer. Defaults to printing results to stdout func (r *Report) PrintResults(printOptions ...PrintOption) { printer := NewPrinter(printOptions...) printer.PrintReport(r) } // Fix tries to automatically patch any security concerns and writes the resulting manifest to the provided writer. // Only applies when audit was performed on a manifest (not local or cluster) func (r *Report) Fix(writer io.Writer) error { fixed, err := fix(r.RawResults()) if err != nil { return err } _, err = writer.Write(fixed) return err } // PrintPlan writes the actions that will be performed by the Fix() function in a human-readable way to the // provided writer. Only applies when audit was performed on a manifest (not local or cluster) func (r *Report) PrintPlan(writer io.Writer) { for _, result := range r.Results() { for _, auditResult := range result.GetAuditResults() { ok, plan := auditResult.FixPlan() if ok { fmt.Fprintln(writer, "* ", plan) } } } } // Auditable is an interface which is implemented by auditors type Auditable interface { Audit(resource k8s.Resource, resources []k8s.Resource) ([]*AuditResult, error) } 07070100000140000081A400000000000000000000000166C635DC0000085D000000000000000000000000000000000000002300000000kubeaudit-0.22.2/kubeaudit_test.gopackage kubeaudit_test import ( "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/config" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/internal/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNew(t *testing.T) { require := require.New(t) allAuditors, err := all.Auditors(config.KubeauditConfig{}) require.NoError(err) auditor, err := kubeaudit.New(allAuditors) require.NoError(err) assert.NotNil(t, auditor) _, err = kubeaudit.New(nil) require.NotNil(err) } func TestAuditLocal(t *testing.T) { if !test.UseKind() { return } require := require.New(t) allAuditors, err := all.Auditors(config.KubeauditConfig{}) require.NoError(err) auditor, err := kubeaudit.New(allAuditors) require.NoError(err) _, err = auditor.AuditLocal("", "", k8sinternal.ClientOptions{}) require.NoError(err) _, err = auditor.AuditLocal("invalid_path", "", k8sinternal.ClientOptions{}) require.NotNil(err) _, err = auditor.AuditLocal("", "invalid_context", k8sinternal.ClientOptions{}) require.NotNil(err) } func TestAuditCluster(t *testing.T) { require := require.New(t) allAuditors, err := all.Auditors(config.KubeauditConfig{}) require.NoError(err) auditor, err := kubeaudit.New(allAuditors) require.NoError(err) _, err = auditor.AuditCluster(k8sinternal.ClientOptions{}) require.NotNil(err) } func TestUnknownResource(t *testing.T) { // Make sure we produce only warning results for resources kubeaudit doesn't know how to audit files := []string{"unknown_resource_type.yml", "custom_resource_definition.yml"} allAuditors, err := all.Auditors(config.KubeauditConfig{}) require.NoError(t, err) for _, file := range files { t.Run(file, func(t *testing.T) { _, report := test.FixSetupMultiple(t, "internal/test/fixtures", file, allAuditors) require.NotNil(t, report) for _, result := range report.Results() { for _, auditResult := range result.GetAuditResults() { assert.Equal(t, kubeaudit.Warn, auditResult.Severity) } } }) } } 07070100000141000081A400000000000000000000000166C635DC000001F4000000000000000000000000000000000000001C00000000kubeaudit-0.22.2/options.gopackage kubeaudit import ( log "github.com/sirupsen/logrus" ) // Option is used to specify the behaviour of Kubeaudit Auditor type Option func(*Kubeaudit) error // WithLogger specifies the log formatter to use func WithLogger(formatter log.Formatter) Option { return func(_ *Kubeaudit) error { log.SetFormatter(formatter) return nil } } func (a *Kubeaudit) parseOptions(opts []Option) error { for _, opt := range opts { if err := opt(a); err != nil { return err } } return nil } 07070100000142000081A400000000000000000000000166C635DC00000383000000000000000000000000000000000000002100000000kubeaudit-0.22.2/options_test.gopackage kubeaudit_test import ( "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/config" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestWithLogger(t *testing.T) { assert := assert.New(t) allAuditors, err := all.Auditors(config.KubeauditConfig{}) require.NoError(t, err) formatter := logrus.Formatter(&logrus.JSONFormatter{}) _, err = kubeaudit.New(allAuditors, kubeaudit.WithLogger(formatter)) assert.NoError(err) assert.Equal(formatter, logrus.StandardLogger().Formatter) formatter = logrus.Formatter(&logrus.TextFormatter{}) assert.NotEqual(formatter, logrus.StandardLogger().Formatter) _, err = kubeaudit.New(allAuditors, kubeaudit.WithLogger(formatter)) assert.NoError(err) assert.Equal(formatter, logrus.StandardLogger().Formatter) } 07070100000143000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001500000000kubeaudit-0.22.2/pkg07070100000144000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001900000000kubeaudit-0.22.2/pkg/fix07070100000145000081A400000000000000000000000166C635DC00000794000000000000000000000000000000000000002800000000kubeaudit-0.22.2/pkg/fix/annotations.gopackage fix import ( "fmt" "github.com/Shopify/kubeaudit/pkg/k8s" ) // FixBySettingPodAnnotation implements PendingFix type BySettingPodAnnotation struct { Key string Value string } // Apply sets the pod annotation to the specified value func (pending *BySettingPodAnnotation) Apply(resource k8s.Resource) []k8s.Resource { objectMeta := k8s.GetPodObjectMeta(resource) if objectMeta.GetAnnotations() == nil { objectMeta.SetAnnotations(map[string]string{}) } objectMeta.GetAnnotations()[pending.Key] = pending.Value return nil } // Plan is a description of what apply will do func (pending *BySettingPodAnnotation) Plan() string { return fmt.Sprintf("Set pod-level annotation '%v' to '%v'", pending.Key, pending.Value) } // FixByAddingPodAnnotation implements PendingFix type ByAddingPodAnnotation struct { Key string Value string } // Apply adds the pod annotation func (pending *ByAddingPodAnnotation) Apply(resource k8s.Resource) []k8s.Resource { objectMeta := k8s.GetPodObjectMeta(resource) if objectMeta.GetAnnotations() == nil { objectMeta.SetAnnotations(map[string]string{}) } objectMeta.GetAnnotations()[pending.Key] = pending.Value return nil } // Plan is a description of what apply will do func (pending *ByAddingPodAnnotation) Plan() string { return fmt.Sprintf("Add pod-level annotation '%v: %v'", pending.Key, pending.Value) } type ByRemovingPodAnnotations struct { Keys []string } // Apply removes the pod annotation func (pending *ByRemovingPodAnnotations) Apply(resource k8s.Resource) []k8s.Resource { objectMeta := k8s.GetPodObjectMeta(resource) if objectMeta.GetAnnotations() == nil { return nil } for _, key := range pending.Keys { delete(objectMeta.GetAnnotations(), key) } return nil } // Plan is a description of what apply will do func (pending *ByRemovingPodAnnotations) Plan() string { return fmt.Sprintf("Remove pod-level annotations '%v'", pending.Keys) } 07070100000146000081A400000000000000000000000166C635DC000007BF000000000000000000000000000000000000002D00000000kubeaudit-0.22.2/pkg/fix/annotations_test.gopackage fix import ( "testing" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" ) func TestFix(t *testing.T) { cases := []struct { testName string pendingFix kubeaudit.PendingFix preFix func(resource k8s.Resource) assertFixed func(t *testing.T, resource k8s.Resource) }{ { testName: "BySettingPodAnnotation", pendingFix: &BySettingPodAnnotation{Key: "mykey", Value: "myvalue"}, preFix: func(resource k8s.Resource) {}, assertFixed: func(t *testing.T, resource k8s.Resource) { annotations := k8s.GetAnnotations(resource) assert.NotNil(t, annotations) val, ok := annotations["mykey"] assert.True(t, ok) assert.Equal(t, "myvalue", val) }, }, { testName: "ByAddingPodAnnotation", pendingFix: &ByAddingPodAnnotation{Key: "mykey", Value: "myvalue"}, preFix: func(resource k8s.Resource) {}, assertFixed: func(t *testing.T, resource k8s.Resource) { annotations := k8s.GetAnnotations(resource) assert.NotNil(t, annotations) val, ok := annotations["mykey"] assert.True(t, ok) assert.Equal(t, "myvalue", val) }, }, { testName: "ByRemovingPodAnnotations", pendingFix: &ByRemovingPodAnnotations{Keys: []string{"mykey", "mykey2"}}, preFix: func(resource k8s.Resource) { k8s.GetPodObjectMeta(resource).SetAnnotations(map[string]string{"mykey": "myvalue", "mykey2": "myvalue2"}) }, assertFixed: func(t *testing.T, resource k8s.Resource) { annotations := k8s.GetAnnotations(resource) _, ok := annotations["mykey"] assert.False(t, ok) _, ok2 := annotations["mykey2"] assert.False(t, ok2) }, }, } for _, tc := range cases { t.Run(tc.testName, func(t *testing.T) { resource := &k8s.PodV1{Spec: v1.PodSpec{}} tc.preFix(resource) assert.NotEmpty(t, tc.pendingFix.Plan()) tc.pendingFix.Apply(resource) tc.assertFixed(t, resource) }) } } 07070100000147000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001900000000kubeaudit-0.22.2/pkg/k8s07070100000148000081A400000000000000000000000166C635DC00000D80000000000000000000000000000000000000002400000000kubeaudit-0.22.2/pkg/k8s/helpers.gopackage k8s import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // NewTrue returns a pointer to a boolean variable set to true func NewTrue() *bool { b := true return &b } // NewFalse returns a pointer to a boolean variable set to false func NewFalse() *bool { return new(bool) } func GetContainers(resource Resource) []*ContainerV1 { podSpec := GetPodSpec(resource) if podSpec == nil { return nil } var containers []*ContainerV1 for i := range podSpec.Containers { containers = append(containers, &podSpec.Containers[i]) } if len(podSpec.InitContainers) > 0 { containers = append(containers, GetInitContainers(resource)...) } return containers } func GetInitContainers(resource Resource) []*ContainerV1 { podSpec := GetPodSpec(resource) if podSpec == nil { return nil } containers := make([]*ContainerV1, len(podSpec.InitContainers)) for i := range podSpec.InitContainers { containers[i] = &podSpec.InitContainers[i] } return containers } // GetAnnotations returns the annotations at the pod level. If the resource does not have pods, then it returns // the least-nested annotations func GetAnnotations(resource Resource) map[string]string { objectMeta := GetPodObjectMeta(resource) if objectMeta != nil { return objectMeta.GetAnnotations() } return nil } // GetLabels returns the labels at the pod level. If the resource does not have pods, then it returns the // least-nested labels func GetLabels(resource Resource) map[string]string { objectMeta := GetPodObjectMeta(resource) if objectMeta != nil { return objectMeta.GetLabels() } return nil } // GetObjectMeta returns the highest-level ObjectMeta func GetObjectMeta(resource Resource) metav1.Object { obj, _ := resource.(metav1.ObjectMetaAccessor) if obj != nil { return obj.GetObjectMeta() } return nil } // GetPodObjectMeta returns the ObjectMeta at the pod level. If the resource does not have pods, then it returns // the highest-level ObjectMeta func GetPodObjectMeta(resource Resource) metav1.Object { podTemplateSpec := GetPodTemplateSpec(resource) if podTemplateSpec != nil { return &podTemplateSpec.ObjectMeta } return GetObjectMeta(resource) } // GetPodSpec gets the PodSpec for a resource. Avoid using this function if you need support for Namespace or // ServiceAccount resources, and write a helper functions in this package instead func GetPodSpec(resource Resource) *PodSpecV1 { podTemplateSpec := GetPodTemplateSpec(resource) if podTemplateSpec != nil { return &podTemplateSpec.Spec } switch kubeType := resource.(type) { case *PodV1: return &kubeType.Spec case *NamespaceV1, *ServiceAccountV1: return nil } return nil } // GetPodTemplateSpec gets the PodTemplateSpec for a resource. Avoid using this function if you need support for // Pod, Namespace, or ServiceAccount resources, and write a helper functions in this package instead func GetPodTemplateSpec(resource Resource) *PodTemplateSpecV1 { switch kubeType := resource.(type) { case *CronJobV1Beta1: return &kubeType.Spec.JobTemplate.Spec.Template case *DaemonSetV1: return &kubeType.Spec.Template case *DeploymentV1: return &kubeType.Spec.Template case *JobV1: return &kubeType.Spec.Template case *PodTemplateV1: return &kubeType.Template case *ReplicationControllerV1: return kubeType.Spec.Template case *StatefulSetV1: return &kubeType.Spec.Template case *PodV1, *NamespaceV1: return nil } return nil } 07070100000149000081A400000000000000000000000166C635DC00000D82000000000000000000000000000000000000002600000000kubeaudit-0.22.2/pkg/k8s/resources.gopackage k8s var podTemplateSpec = PodTemplateSpecV1{ ObjectMeta: ObjectMetaV1{}, Spec: PodSpecV1{}, } // NewDeployment creates a new Deployment resource func NewDeployment() *DeploymentV1 { return &DeploymentV1{ TypeMeta: TypeMetaV1{ Kind: "Deployment", APIVersion: "apps/v1", }, ObjectMeta: ObjectMetaV1{}, Spec: DeploymentSpecV1{ Template: podTemplateSpec, }, } } // NewPod creates a new Pod resource func NewPod() *PodV1 { return &PodV1{ TypeMeta: TypeMetaV1{ Kind: "Pod", APIVersion: "v1", }, ObjectMeta: ObjectMetaV1{}, Spec: PodSpecV1{}, } } // NewNamespace creates a new Namespace resource func NewNamespace() *NamespaceV1 { return &NamespaceV1{ TypeMeta: TypeMetaV1{ Kind: "Namespace", APIVersion: "v1", }, ObjectMeta: ObjectMetaV1{}, Spec: NamespaceSpecV1{}, } } // NewDaemonSet creates a new DaemonSet resource func NewDaemonSet() *DaemonSetV1 { return &DaemonSetV1{ TypeMeta: TypeMetaV1{ Kind: "DaemonSet", APIVersion: "apps/v1", }, ObjectMeta: ObjectMetaV1{}, Spec: DaemonSetSpecV1{ Template: podTemplateSpec, }, } } // NewReplicationController creates a new ReplicationController resource func NewReplicationController() *ReplicationControllerV1 { return &ReplicationControllerV1{ TypeMeta: TypeMetaV1{ Kind: "ReplicationController", APIVersion: "v1", }, ObjectMeta: ObjectMetaV1{}, Spec: ReplicationControllerSpecV1{ Template: podTemplateSpec.DeepCopy(), }, } } // NewStatefulSet creates a new StatefulSet resource func NewStatefulSet() *StatefulSetV1 { return &StatefulSetV1{ TypeMeta: TypeMetaV1{ Kind: "StatefulSet", APIVersion: "apps/v1", }, ObjectMeta: ObjectMetaV1{}, Spec: StatefulSetSpecV1{ Template: podTemplateSpec, }, } } // NewNetworkPolicy creates a new NetworkPolicy resource func NewNetworkPolicy() *NetworkPolicyV1 { return &NetworkPolicyV1{ TypeMeta: TypeMetaV1{ Kind: "NetworkPolicy", APIVersion: "networking.k8s.io/v1", }, ObjectMeta: ObjectMetaV1{}, Spec: NetworkPolicySpecV1{}, } } // NewPodTemplate creates a new PodTemplate resource func NewPodTemplate() *PodTemplateV1 { return &PodTemplateV1{ TypeMeta: TypeMetaV1{ Kind: "PodTemplate", APIVersion: "v1", }, ObjectMeta: ObjectMetaV1{}, Template: podTemplateSpec, } } // NewCronJob creates a new CronJob resource func NewCronJob() *CronJobV1Beta1 { return &CronJobV1Beta1{ TypeMeta: TypeMetaV1{ Kind: "CronJob", APIVersion: "batch/v1beta1", }, ObjectMeta: ObjectMetaV1{}, Spec: CronJobSpecV1Beta1{ JobTemplate: JobTemplateSpecV1Beta1{ Spec: JobSpecV1{ Template: podTemplateSpec, }, }, }, } } // NewServiceAccount creates a new ServiceAccount resource func NewServiceAccount() *ServiceAccountV1 { return &ServiceAccountV1{ TypeMeta: TypeMetaV1{ Kind: "ServiceAccount", APIVersion: "v1", }, ObjectMeta: ObjectMetaV1{}, } } // NewService creates a new Service resource func NewService() *ServiceV1 { return &ServiceV1{ TypeMeta: TypeMetaV1{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: ObjectMetaV1{}, } } // NewJob creates a new Job resource func NewJob() *JobV1 { return &JobV1{ TypeMeta: TypeMetaV1{ Kind: "Job", APIVersion: "batch/v1", }, ObjectMeta: ObjectMetaV1{}, Spec: JobSpecV1{ Template: podTemplateSpec, }, } } 0707010000014A000081A400000000000000000000000166C635DC000000BA000000000000000000000000000000000000002800000000kubeaudit-0.22.2/pkg/k8s/type_checks.gopackage k8s func IsNamespaceV1(resource Resource) bool { _, ok := resource.(*NamespaceV1) return ok } func IsPodV1(resource Resource) bool { _, ok := resource.(*PodV1) return ok } 0707010000014B000081A400000000000000000000000166C635DC0000106D000000000000000000000000000000000000002200000000kubeaudit-0.22.2/pkg/k8s/types.gopackage k8s import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" apiv1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sRuntime "k8s.io/apimachinery/pkg/runtime" ) // CapabilitiesV1 is a type alias for the v1 version of the k8s API. type CapabilitiesV1 = apiv1.Capabilities // CapabilityV1 is a type alias for the v1 version of the k8s API. type CapabilityV1 = apiv1.Capability // ContainerV1 is a type alias for the v1 version of the k8s API. type ContainerV1 = apiv1.Container // CronJobV1Beta1 is a type alias for the v1beta1 version of the k8s batch API. type CronJobV1Beta1 = batchv1beta1.CronJob // CronJobSpecV1Beta1 is a type alias for the v1beta1 version of the k8s batch API. type CronJobSpecV1Beta1 = batchv1beta1.CronJobSpec // DaemonSetSpecV1 is a type alias for the v1 version of the k8s apps API. type DaemonSetSpecV1 = appsv1.DaemonSetSpec // DaemonSetV1 is a type alias for the v1 version of the k8s API. type DaemonSetV1 = appsv1.DaemonSet // DeploymentSpecV1 is a type alias for the v1 version of the k8s apps API. type DeploymentSpecV1 = appsv1.DeploymentSpec // DeploymentV1 is a type alias for the v1 version of the k8s apps API. type DeploymentV1 = appsv1.Deployment // JobTemplateSpecV1Beta1 is a type alias for the v1beta1 version of the k8s batch API. type JobTemplateSpecV1Beta1 = batchv1beta1.JobTemplateSpec // JobSpecV1 is a type alias for the v1 version of the k8s batch API. type JobSpecV1 = batchv1.JobSpec // JobV1 is a type alias for the v1 version of the k8s batch API. type JobV1 = batchv1.Job // ListOptionsV1 is a type alias for the v1 version of the k8s meta API. type ListOptionsV1 = metav1.ListOptions // NamespaceV1 is a type alias for the v1 version of the k8s API. type NamespaceV1 = apiv1.Namespace // NamespaceSpecV1 is a type alias for the v1 version of the k8s API. type NamespaceSpecV1 = apiv1.NamespaceSpec // NetworkPolicySpecV1 is a type alias for the v1 version of the k8s networking API. type NetworkPolicySpecV1 = networkingv1.NetworkPolicySpec // NetworkPolicyV1 is a type alias for the v1 version of the k8s networking API. type NetworkPolicyV1 = networkingv1.NetworkPolicy // ObjectMetaV1 is a type alias for the v1 version of the k8s meta API. type ObjectMetaV1 = metav1.ObjectMeta // PodSpecV1 is a type alias for the v1 version of the k8s API. type PodSpecV1 = apiv1.PodSpec // PodTemplateSpecV1 is a type alias for the v1 version of the k8s API. type PodTemplateSpecV1 = apiv1.PodTemplateSpec // PodTemplateV1 is a type alias for the v1 version of the k8s API. type PodTemplateV1 = apiv1.PodTemplate // PodV1 is a type alias for the v1 version of the k8s API. type PodV1 = apiv1.Pod // PolicyTypeV1 is a type alias for the v1 version of the k8s networking API. type PolicyTypeV1 = networkingv1.PolicyType // ReplicationControllerSpecV1 is a type alias for the v1 version of the k8s API. type ReplicationControllerSpecV1 = apiv1.ReplicationControllerSpec // ReplicationControllerV1 is a type alias for the v1 version of the k8s API. type ReplicationControllerV1 = apiv1.ReplicationController // Resource is a type alias for a runtime.Object type Resource k8sRuntime.Object // SecurityContextV1 is a type alias for the v1 version of the k8s API. type SecurityContextV1 = apiv1.SecurityContext // ServiceAccountV1 is a type alias for the v1 version of the k8s API. type ServiceAccountV1 = apiv1.ServiceAccount // ServiceV1 is a type alias for the v1 version of the k8s API. type ServiceV1 = apiv1.Service // ServiceV1Spec is a type alias for the v1 version of the k8s API. type ServiceV1Spec = apiv1.ServiceSpec // StatefulSetSpecV1 is a type alias for the v1 version of the k8s apps API. type StatefulSetSpecV1 = appsv1.StatefulSetSpec // StatefulSetV1 is a type alias for the v1 version of the k8s apps API. type StatefulSetV1 = appsv1.StatefulSet // TypeMetaV1 is a type alias for the v1 version of the k8s meta API. type TypeMetaV1 = metav1.TypeMeta // UnsupportedType is a type alias for v1 version of the k8s apps API, this is meant for testing type UnsupportedType = apiv1.Binding 0707010000014C000041ED00000000000000000000000266C635DC00000000000000000000000000000000000000000000001E00000000kubeaudit-0.22.2/pkg/override0707010000014D000081A400000000000000000000000166C635DC00001630000000000000000000000000000000000000002A00000000kubeaudit-0.22.2/pkg/override/override.gopackage override import ( "strings" "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/pkg/k8s" ) const ( // TODO: remove deprecated unregistered labels after warning users about the breaking change // DeprecatedContainerOverrideLabelPrefix is used to disable an auditor for a specific container DeprecatedContainerOverrideLabelPrefix = "container.audit.kubernetes.io/" // DeprecatedPodOverrideLabelPrefix is used to disable an auditor for a specific pod DeprecatedPodOverrideLabelPrefix = "audit.kubernetes.io/pod." // DeprecatedNamespaceOverrideLabelPrefix is used to disable an auditor for a specific namespace resource DeprecatedNamespaceOverrideLabelPrefix = "audit.kubernetes.io/namespace." // ContainerOverrideLabelPrefix is used to disable an auditor for a specific container ContainerOverrideLabelPrefix = "container.kubeaudit.io/" // OverrideLabelPrefix is used to disable an auditor for either a pod or namespace OverrideLabelPrefix = "kubeaudit.io/" ) // GetOverriddenResultName takes an audit result name and modifies it to indicate that the security issue was // ignored by an override label func GetOverriddenResultName(resultName string) string { return resultName + "Allowed" } // NewRedundantOverrideResult creates a new AuditResult at warning level telling the user to remove the override // label because there are no security issues found, so the label is redundant func NewRedundantOverrideResult(auditorName, containerName, overrideReason, overrideLabel string) *kubeaudit.AuditResult { return &kubeaudit.AuditResult{ Auditor: auditorName, Rule: kubeaudit.RedundantAuditorOverride, Severity: kubeaudit.Warn, Message: "Auditor is disabled via label but there were no security issues found by the auditor. The label should be removed.", Metadata: kubeaudit.Metadata{ "Container": containerName, "OverrideLabel": overrideLabel, }, } } // ApplyOverride checks if hasOverride is true. If it is, it changes the severity of the audit result from error to // info, adds the override reason to the metadata and removes the pending fix func ApplyOverride(auditResult *kubeaudit.AuditResult, auditorName, containerName string, resource k8s.Resource, overrideLabel string) *kubeaudit.AuditResult { hasOverride, overrideReason := GetContainerOverrideReason(containerName, resource, overrideLabel) if !hasOverride { return auditResult } if auditResult == nil { return NewRedundantOverrideResult(auditorName, containerName, overrideReason, overrideLabel) } auditResult.Rule = GetOverriddenResultName(auditResult.Rule) auditResult.PendingFix = nil auditResult.Severity = kubeaudit.Info auditResult.Message = "Audit result overridden: " + auditResult.Message if overrideReason != "" && strings.ToLower(overrideReason) != "true" { if auditResult.Metadata == nil { auditResult.Metadata = make(kubeaudit.Metadata) } auditResult.Metadata["OverrideReason"] = overrideReason } return auditResult } // GetContainerOverrideReason returns true if the resource has a pod-level label disabling a given auditor and the // value of the label which is meant to represent the reason for overriding the auditor // // Container override labels disable the auditor for that specific container and have the following format: // // container.kubeaudit.io/[container name].[auditor override label] // // If there is no container override label, it calls GetResourceOverrideReason() func GetContainerOverrideReason(containerName string, resource k8s.Resource, overrideLabel string) (hasOverride bool, reason string) { labels := k8s.GetLabels(resource) if containerName != "" { if reason, hasOverride = labels[GetDeprecatedContainerOverrideLabel(containerName, overrideLabel)]; hasOverride { return } if reason, hasOverride = labels[GetContainerOverrideLabel(containerName, overrideLabel)]; hasOverride { return } } return GetResourceOverrideReason(resource, overrideLabel) } // GetResourceOverrideReason returns true if the resource has a label disabling a given auditor and the value of the // label which is meant to represent the reason for overriding the auditor // // Pod override labels disable the auditor for the pod and all containers within the pod and have the following format: // // kubeaudit.io/[auditor override label] // // Namespace override labels disable the auditor for the namespace resource and have the following format: // // kubeaudit.io/[auditor override label] func GetResourceOverrideReason(resource k8s.Resource, auditorOverrideLabel string) (hasOverride bool, reason string) { labelFuncs := []func(overrideLabel string) string{ GetOverrideLabel, GetDeprecatedPodOverrideLabel, GetDeprecatedNamespaceOverrideLabel, } labels := k8s.GetLabels(resource) for _, getLabel := range labelFuncs { if reason, hasOverride = labels[getLabel(auditorOverrideLabel)]; hasOverride { return } } return false, "" } // TODO: remove deprecated getters func GetDeprecatedPodOverrideLabel(overrideLabel string) string { return DeprecatedPodOverrideLabelPrefix + overrideLabel } func GetDeprecatedNamespaceOverrideLabel(overrideLabel string) string { return DeprecatedNamespaceOverrideLabelPrefix + overrideLabel } func GetDeprecatedContainerOverrideLabel(containerName, overrideLabel string) string { return DeprecatedContainerOverrideLabelPrefix + containerName + "." + overrideLabel } func GetOverrideLabel(overrideLabel string) string { return OverrideLabelPrefix + overrideLabel } func GetContainerOverrideLabel(containerName, overrideLabel string) string { return ContainerOverrideLabelPrefix + containerName + "." + overrideLabel } 0707010000014E000081A400000000000000000000000166C635DC000013E4000000000000000000000000000000000000001C00000000kubeaudit-0.22.2/printer.gopackage kubeaudit import ( "fmt" "io" "os" "github.com/Shopify/kubeaudit/internal/color" "github.com/Shopify/kubeaudit/pkg/k8s" log "github.com/sirupsen/logrus" ) type Printer struct { writer io.Writer minSeverity SeverityLevel formatter log.Formatter color bool } type PrintOption func(p *Printer) // WithMinSeverity sets the minimum severity of results that will be printed. func WithMinSeverity(minSeverity SeverityLevel) PrintOption { return func(p *Printer) { p.minSeverity = minSeverity } } // WithWriter sets the writer where results will be written to. func WithWriter(writer io.Writer) PrintOption { return func(p *Printer) { p.writer = writer } } // WithFormatter sets a logrus formatter to use to format results. func WithFormatter(formatter log.Formatter) PrintOption { return func(p *Printer) { p.formatter = formatter } } // WithColor specifies whether or not to colorize output. You will likely want to set this to false if // not writing to standard out. func WithColor(color bool) PrintOption { return func(p *Printer) { p.color = color } } func (p *Printer) parseOptions(opts ...PrintOption) { for _, opt := range opts { opt(p) } } func NewPrinter(opts ...PrintOption) Printer { p := Printer{ writer: os.Stdout, minSeverity: Info, color: true, } p.parseOptions(opts...) return p } func (p *Printer) PrintReport(report *Report) { if p.formatter == nil { p.prettyPrintReport(report) } else { p.logReport(report) } } func (p *Printer) prettyPrintReport(report *Report) { if len(report.ResultsWithMinSeverity(p.minSeverity)) < 1 { p.printColor(color.GreenColor, "All checks completed. 0 high-risk vulnerabilities found\n") return } for _, workloadResult := range report.ResultsWithMinSeverity(p.minSeverity) { resource := workloadResult.GetResource().Object() objectMeta := k8s.GetObjectMeta(resource) resouceApiVersion, resourceKind := resource.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind() p.printColor(color.CyanColor, "\n---------------- Results for ---------------\n\n") p.printColor(color.CyanColor, " apiVersion: "+resouceApiVersion+"\n") p.printColor(color.CyanColor, " kind: "+resourceKind+"\n") if objectMeta != nil && (objectMeta.GetName() != "" || objectMeta.GetNamespace() != "") { p.printColor(color.CyanColor, " metadata:\n") if objectMeta.GetName() != "" { p.printColor(color.CyanColor, " name: "+objectMeta.GetName()+"\n") } if objectMeta.GetNamespace() != "" { p.printColor(color.CyanColor, " namespace: "+objectMeta.GetNamespace()+"\n") } } p.printColor(color.CyanColor, "\n--------------------------------------------\n\n") for _, auditResult := range workloadResult.GetAuditResults() { severityColor := color.YellowColor switch auditResult.Severity { case Info: severityColor = color.CyanColor case Warn: severityColor = color.YellowColor case Error: severityColor = color.RedColor } p.print("-- ") p.printColor(severityColor, "["+auditResult.Severity.String()+"] ") p.print(auditResult.Rule + "\n") p.print(" Message: " + auditResult.Message + "\n") if len(auditResult.Metadata) > 0 { p.print(" Metadata:\n") } for k, v := range auditResult.Metadata { p.print(fmt.Sprintf(" %s: %s\n", k, v)) } p.print("\n") } } } func (p *Printer) print(s string) { fmt.Fprint(p.writer, s) } func (p *Printer) printColor(c string, s string) { if p.color { fmt.Fprint(p.writer, color.Colored(c, s)) } else { p.print(s) } } func (p *Printer) logReport(report *Report) { resultLogger := log.New() resultLogger.SetOutput(p.writer) resultLogger.SetFormatter(p.formatter) // We manually manage what severity levels to log, logrus should let everything through resultLogger.SetLevel(log.DebugLevel) for _, workloadResult := range report.ResultsWithMinSeverity(p.minSeverity) { for _, auditResult := range workloadResult.GetAuditResults() { p.logAuditResult(workloadResult.GetResource().Object(), auditResult, resultLogger) } } } func (p *Printer) logAuditResult(resource k8s.Resource, result *AuditResult, baseLogger *log.Logger) { logger := baseLogger.WithFields(p.getLogFieldsForResult(resource, result)) switch result.Severity { case Info: logger.Info(result.Message) case Warn: logger.Warn(result.Message) case Error: logger.Error(result.Message) } } func (p *Printer) getLogFieldsForResult(resource k8s.Resource, result *AuditResult) log.Fields { apiVersion, kind := resource.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind() objectMeta := k8s.GetObjectMeta(resource) fields := log.Fields{ "AuditResultName": result.Rule, "ResourceKind": kind, "ResourceApiVersion": apiVersion, } if objectMeta != nil { if objectMeta.GetNamespace() != "" { fields["ResourceNamespace"] = objectMeta.GetNamespace() } if objectMeta.GetName() != "" { fields["ResourceName"] = objectMeta.GetName() } } for k, v := range result.Metadata { fields[k] = v } return fields } 0707010000014F000081A400000000000000000000000166C635DC00000ADF000000000000000000000000000000000000001B00000000kubeaudit-0.22.2/result.gopackage kubeaudit import "github.com/Shopify/kubeaudit/pkg/k8s" // AuditResult severity levels. They also correspond to log levels const ( // Info is used for informational audit results where no action is required Info SeverityLevel = 0 // Warn is used for audit results where there may be security concerns. If an auditor is disabled for a resource // using an override label, the audit results will be warnings instead of errors. Kubeaudit will NOT attempt to // fix these Warn SeverityLevel = 1 // Error is used for audit results where action is required. Kubeaudit will attempt to fix these Error SeverityLevel = 2 ) // Result contains the audit results for a single Kubernetes resource type Result interface { GetResource() KubeResource GetAuditResults() []*AuditResult } type SeverityLevel int func (s SeverityLevel) String() string { switch s { case Info: return "info" case Warn: return "warning" case Error: return "error" default: return "unknown" } } // AuditResult represents a potential security issue. There may be multiple AuditResults per resource and audit type AuditResult struct { Auditor string // Auditor name Rule string // Rule uniquely identifies a type of violation Severity SeverityLevel // Severity is one of Error, Warn, or Info Message string // Message is a human-readable description of the audit result PendingFix PendingFix // PendingFix is the fix that will be applied to automatically fix the security issue Metadata Metadata // Metadata includes additional context for an audit result FilePath string // Manifest file path } func (result *AuditResult) Fix(resource k8s.Resource) (newResources []k8s.Resource) { if result.PendingFix == nil { return nil } return result.PendingFix.Apply(resource) } func (result *AuditResult) FixPlan() (ok bool, plan string) { if result.PendingFix == nil { return false, "" } return true, result.PendingFix.Plan() } // PendingFix includes the logic to automatically fix the issues caught by auditing type PendingFix interface { // Plan returns a human-readable description of what Apply() will do Plan() string // Apply applies the proposed fix to the resource and returns any new resources that were created. Note that // Apply is expected to modify the passed in resource Apply(k8s.Resource) []k8s.Resource } // Metadata holds metadata for a potential security issue type Metadata = map[string]string // Implements Result type WorkloadResult struct { Resource KubeResource AuditResults []*AuditResult } func (wlResult *WorkloadResult) GetResource() KubeResource { return wlResult.Resource } func (wlResult *WorkloadResult) GetAuditResults() []*AuditResult { return wlResult.AuditResults } 07070100000150000081ED00000000000000000000000166C635DC000001DC000000000000000000000000000000000000001900000000kubeaudit-0.22.2/test.sh#!/usr/bin/env bash set -e echo "Starting tests. This may take a while..." function finish { if [ "$USE_KIND" == "true" ] ; then make test-teardown fi } trap finish EXIT if [ "$USE_KIND" == "true" ] ; then make test-setup fi touch coverage.txt for d in $(go list ./...); do go test -race -coverprofile=profile.out -covermode=atomic $d if [ -f profile.out ]; then cat profile.out >> coverage.txt rm profile.out fi done 07070100000151000081A400000000000000000000000166C635DC000008B1000000000000000000000000000000000000001900000000kubeaudit-0.22.2/util.gopackage kubeaudit import ( "bytes" "fmt" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/pkg/k8s" "gopkg.in/yaml.v3" ) func getResourcesFromClient(client k8sinternal.KubeClient, options k8sinternal.ClientOptions) ([]KubeResource, error) { var resources []KubeResource k8sresources, err := client.GetAllResources(options) if err != nil { return nil, err } for _, resource := range k8sresources { resources = append(resources, &kubeResource{object: resource}) } return resources, nil } func getResourcesFromManifest(data []byte) ([]KubeResource, error) { var resources []KubeResource bufSlice := bytes.Split(data, []byte("---")) for _, b := range bufSlice { obj, err := k8sinternal.DecodeResource(b) if err == nil && obj != nil { source := &kubeResource{ object: obj, bytes: b, } resources = append(resources, source) } else if err := yaml.Unmarshal(data, &yaml.Node{}); err != nil { return nil, fmt.Errorf("Invalid yaml: %w", err) } else { resources = append(resources, &kubeResource{bytes: b}) } } return resources, nil } func auditResources(resources []KubeResource, auditable []Auditable) ([]Result, error) { var results []Result for _, resource := range resources { result, err := auditResource(resource, resources, auditable) if err != nil { return nil, err } results = append(results, result) } return results, nil } func auditResource(resource KubeResource, resources []KubeResource, auditables []Auditable) (Result, error) { result := &WorkloadResult{ Resource: resource, AuditResults: []*AuditResult{}, } if resource.Object() == nil { return result, nil } for _, auditable := range auditables { auditResults, err := auditable.Audit(resource.Object(), unwrapResources(resources)) if err != nil { return nil, err } result.AuditResults = append(result.AuditResults, auditResults...) } return result, nil } func unwrapResources(resources []KubeResource) []k8s.Resource { unwrappedResources := make([]k8s.Resource, 0, len(resources)) for _, resource := range resources { unwrappedResources = append(unwrappedResources, resource.Object()) } return unwrappedResources } 07070100000152000081A400000000000000000000000166C635DC00000B3E000000000000000000000000000000000000001E00000000kubeaudit-0.22.2/util_test.gopackage kubeaudit import ( "bytes" "encoding/json" "testing" "github.com/Shopify/kubeaudit/pkg/k8s" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) type logEntry struct { AuditResultName string Foo string Level string `json:"level"` ResourceKind string ResourceApiVersion string ResourceName string ResourceNamespace string } func TestPrintResults(t *testing.T) { report := Report{ results: []Result{ &WorkloadResult{ AuditResults: []*AuditResult{ newTestAuditResult(Error), newTestAuditResult(Warn), newTestAuditResult(Info), }, Resource: &kubeResource{ object: k8s.NewPod(), }, }, }, } out := bytes.NewBuffer(nil) writerOption := WithWriter(out) formatterOption := WithFormatter(&log.JSONFormatter{}) // Error results only report.PrintResults(writerOption, WithMinSeverity(Error), formatterOption) assert.Equal(t, 1, bytes.Count(out.Bytes(), []byte{'\n'})) out.Reset() // Error and warn results report.PrintResults(writerOption, WithMinSeverity(Warn), formatterOption) assert.Equal(t, 2, bytes.Count(out.Bytes(), []byte{'\n'})) out.Reset() // Error, warn, and info results report.PrintResults(writerOption, WithMinSeverity(Info), formatterOption) assert.Equal(t, 3, bytes.Count(out.Bytes(), []byte{'\n'})) } func newTestAuditResult(severity SeverityLevel) *AuditResult { return &AuditResult{ Rule: "MyAuditResult", Severity: severity, Metadata: Metadata{"Foo": "bar"}, } } func TestLogAuditResult(t *testing.T) { for _, severity := range []SeverityLevel{Error, Warn, Info} { // Send all log output as JSON to this byte buffer out := bytes.NewBuffer(nil) resource := k8s.NewDeployment() resource.Name = "mydeployment" resource.Namespace = "mynamespace" auditResult := newTestAuditResult(severity) report := &Report{ results: []Result{ &WorkloadResult{ AuditResults: []*AuditResult{ auditResult, }, Resource: &kubeResource{ object: resource, }, }, }, } expectedApiVersion, expectedKind := resource.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind() expected := logEntry{ AuditResultName: "MyAuditResult", Level: severity.String(), Foo: auditResult.Metadata["Foo"], ResourceKind: expectedKind, ResourceApiVersion: expectedApiVersion, ResourceName: resource.GetName(), ResourceNamespace: resource.GetNamespace(), } // This writes the log to the variable out, parses the JSON into the logEntry struct, and checks the struct printer := NewPrinter(WithWriter(out), WithFormatter(&log.JSONFormatter{})) printer.PrintReport(report) got := logEntry{} err := json.Unmarshal(out.Bytes(), &got) assert.NoError(t, err) assert.Equal(t, expected, got) out.Reset() } } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1261 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor