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[](https://github.com/Shopify/kubeaudit/actions)
[](https://goreportcard.com/report/github.com/Shopify/kubeaudit)
[](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