Compare commits

..
52 Commits
Author SHA1 Message Date
Robert Brennan e9bf38fe28 Update triage.yml 2022-08-09 13:00:27 -04:00
Robert Brennan 4601c1fff4 Update triage.yml 2022-08-09 12:46:03 -04:00
Robert Brennan e81c2d3930 Update triage.yml 2022-08-09 12:42:52 -04:00
Robert Brennan 9f33a603e4 Update triage.yml 2022-08-09 12:42:07 -04:00
Robert Brennan 7bae85c493 Update triage.yml 2022-08-09 12:37:55 -04:00
Robert Brennan 468bc2ef64 Update triage.yml 2022-08-09 12:30:48 -04:00
Robert Brennan ab2d52b8d8 Update triage.yml 2022-08-09 12:21:36 -04:00
Robert Brennan b079665364 Update triage.yml 2022-08-09 12:20:31 -04:00
Robert Brennan baad393ea5 Update triage.yml 2022-08-09 12:17:28 -04:00
Robert Brennan 025cac8981 Update triage.yml 2022-08-09 12:15:09 -04:00
Robert Brennan f229f4d663 Update triage.yml 2022-08-09 12:09:33 -04:00
Robert Brennan e1cbe4ce6a Update triage.yml 2022-08-09 12:00:22 -04:00
Robert Brennan 5d91a5f7e0 Update triage.yml 2022-08-09 11:54:00 -04:00
Robert Brennan c5ac49583e Update triage.yml 2022-08-09 11:52:13 -04:00
Robert Brennan 8ff5ad679a Update triage.yml 2022-08-09 11:51:25 -04:00
Robert Brennan c09dac5262 Update triage.yml 2022-08-09 11:49:52 -04:00
Robert Brennan 7c675c6d43 Update triage.yml 2022-08-09 11:40:50 -04:00
Robert Brennan 6ef6193c3d Update triage.yml 2022-08-09 10:35:25 -04:00
Robert Brennan bd9dd660ff Update triage.yml 2022-08-09 10:35:14 -04:00
Robert Brennan 559736b1b2 Update triage.yml 2022-08-09 10:33:51 -04:00
Robert Brennan ef4b6ece30 Update triage.yml 2022-08-09 10:32:34 -04:00
Robert Brennan 34152609a8 Update triage.yml 2022-08-09 10:27:00 -04:00
Robert Brennan 0879f2a040 Update triage.yml 2022-08-09 10:22:17 -04:00
Robert Brennan a2123293f7 Update triage.yml 2022-08-09 10:19:42 -04:00
Robert Brennan a5acc96001 Update triage.yml 2022-08-09 10:17:22 -04:00
Robert Brennan e2e7918d36 Update triage.yml 2022-08-09 10:10:22 -04:00
Robert Brennan ad7b4f6aee Update triage.yml 2022-08-09 10:03:27 -04:00
Robert Brennan 581d06c194 Update triage.yml 2022-08-09 10:02:25 -04:00
Robert Brennan 729e7a9482 Update triage.yml 2022-08-09 10:01:01 -04:00
Robert Brennan a63756a158 Update triage.yml 2022-08-09 09:58:15 -04:00
Robert Brennan affab91d92 Update triage.yml 2022-08-09 09:55:28 -04:00
Robert Brennan ccd707f230 Update triage.yml 2022-08-09 09:53:17 -04:00
Robert Brennan 68064da185 Update triage.yml 2022-08-09 09:52:46 -04:00
Robert Brennan e4758dbc44 Update triage.yml 2022-08-09 09:50:20 -04:00
Robert Brennan 09209c9a7b Update triage.yml 2022-08-09 09:48:45 -04:00
Robert Brennan 8a7e691a8e Update triage.yml 2022-08-09 09:39:34 -04:00
Robert Brennan 3440296557 Update triage.yml 2022-08-09 09:37:04 -04:00
Robert Brennan 511157e010 Update triage.yml 2022-08-09 09:36:15 -04:00
Robert Brennan 195b9a8860 Update triage.yml 2022-08-09 09:35:41 -04:00
Robert Brennan 93b251d5c0 Update triage.yml 2022-08-09 09:33:40 -04:00
Robert Brennan af840db564 Update triage.yml 2022-08-09 09:32:30 -04:00
Robert Brennan 909bc5e86b Update triage.yml 2022-08-09 09:29:33 -04:00
Robert Brennan 3cdaf143a4 Update triage.yml 2022-08-09 09:27:50 -04:00
Robert Brennan 693dbc5b25 Update triage.yml 2022-08-09 09:03:02 -04:00
Robert Brennan b77672bde8 Update triage.yml 2022-08-08 18:14:48 -04:00
Robert Brennan bf96f1dc89 Update triage.yml 2022-08-08 18:07:50 -04:00
Robert Brennan 40c6c569e0 Update triage.yml 2022-08-08 16:05:11 -04:00
Robert Brennan fe014ac2bf Update triage.yml 2022-08-08 16:04:18 -04:00
Robert Brennan 5f87baaa56 Update triage.yml 2022-08-08 16:01:53 -04:00
Robert Brennan 35e7896553 Update triage.yml 2022-08-08 15:55:39 -04:00
Robert Brennan 1d7d3433c1 Update triage.yml 2022-08-08 15:51:21 -04:00
Robert Brennan 3a421412bb Create triage.yml 2022-08-08 15:43:45 -04:00
221 changed files with 4891 additions and 10690 deletions
+108 -32
View File
@@ -9,17 +9,6 @@ executors:
machine:
enabled: true
commands:
install_goreleaser_dependencies:
description: Installs dependencies for CI scripts
steps:
- run: apk update
# gettext provides envsubst
- run: apk add gettext
# Register other docker platforms, to build arm64.
# This shouldn't be needed, why TBD.
- run: docker run --privileged --rm tonistiigi/binfmt --install all
references:
set_environment_variables: &set_environment_variables
run:
@@ -98,6 +87,32 @@ references:
echo "Skipping Kubernetes tests for forked PR"
fi
docker_build: &docker_build
run:
name: Docker login and build
command: |
docker-pull -f .circleci/build.config
docker-build -f .circleci/build.config
docker_build_and_push: &docker_build_and_push
run:
name: Docker login, build, and push
command: |
docker-pull -f .circleci/build.config
docker-build -f .circleci/build.config
docker login quay.io -u="${fairwinds_quay_user}" -p="${fairwinds_quay_token}"
docker-push -f .circleci/build.config
enable_experimental_features: &enable_experimental_docker_features
run:
name: enable experimental features
command: |
set -ex
apk --update add openssh
ssh remote-docker \<<EOF
sudo bash -c 'echo "{\"experimental\": true}" > /etc/docker/daemon.json'
sudo systemctl restart docker
EOF
install_vault_alpine: &install_vault_alpine
run:
name: install hashicorp vault
@@ -109,6 +124,24 @@ references:
mv vault /usr/bin/vault
jobs:
build:
docker:
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
- *set_environment_variables
- *docker_build
push:
docker:
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
- *set_environment_variables
- *docker_build_and_push
test_k8s:
working_directory: ~/polaris
resource_class: medium
@@ -119,62 +152,105 @@ jobs:
- *test_k8s
test:
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: cimg/go:1.19
- image: circleci/golang:1.17
steps:
- checkout
- *set_environment_variables
- run: go vet ./...
- run: go get -u golang.org/x/lint/golint
- run: go list ./... | grep -v vendor | xargs golint -set_exit_status
- run: go list ./... | grep -v vendor | xargs go vet
- run: go test ./... -coverprofile=coverage.txt -covermode=count
- *test_binary_dashboard
build_and_push:
insights:
docker:
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
- run:
name: Insights CI
command: curl -L https://insights.fairwinds.com/v0/insights-ci.sh | bash
release_binary:
working_directory: /go/src/github.com/fairwindsops/polaris/
resource_class: large
shell: /bin/bash
docker:
# The goreleaser image tag determins the version of Go.
# Manually check goreleaser images for their version of Go.
# Ref: https://hub.docker.com/r/goreleaser/goreleaser/tags
- image: goreleaser/goreleaser:v1.11.4
- image: goreleaser/goreleaser:v1.3.0
steps:
- checkout
- setup_remote_docker:
version: 20.10.11
version: 20.10.6
- *enable_experimental_docker_features
- *install_vault_alpine
- rok8s/get_vault_env:
vault_path: repo/global/env
- rok8s/get_vault_env:
vault_path: repo/polaris/env
- *set_environment_variables
- run: docker login quay.io -u="${FAIRWINDS_QUAY_USER}" -p="${FAIRWINDS_QUAY_TOKEN}"
- install_goreleaser_dependencies
- run: scripts/goreleaser.sh
- run: go get -u github.com/gobuffalo/packr/v2/packr2
- run: git checkout -- . # FIXME: the go get makes the directory dirty
- run: packr2
- run: echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
- run: goreleaser
release_images:
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
- *set_environment_variables
- *docker_build_and_push
workflows:
version: 2
release:
build:
jobs:
- test:
filters:
tags:
only: /.*/
- build_and_push:
context: org-global
- test
- build:
requires:
- test
- push:
context: org-global
requires:
- build
filters:
branches:
ignore: /pull\/[0-9]+/
- insights:
requires:
- push
filters:
branches:
ignore: /pull\/[0-9]+/
tags:
ignore: /^testing-.*/
- test_k8s:
requires:
- build_and_push
- push
filters:
branches:
ignore: /pull\/[0-9]+/
release:
jobs:
- release_binary:
context: org-global
filters:
branches:
ignore: /.*/
tags:
ignore: /^testing-.*/
- release_images:
requires:
- release_binary
context: org-global
filters:
branches:
ignore: /.*/
tags:
ignore: /^testing-.*/
- oss-docs/publish-docs:
+1 -1
View File
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: [triage, enhancement]
labels: ''
assignees: ''
---
+1 -1
View File
@@ -2,7 +2,7 @@
name: Other
about: For misc. tasks like research or continued conversation
title: ''
labels: [triage]
labels: ''
assignees: ''
---
+1 -1
View File
@@ -1,6 +1,6 @@
# The action uses an own Dockerfile on purpose because the root Dockerfile takes way too long to build for an action
FROM alpine:3.17
FROM alpine:3.10
RUN apk add --no-cache \
bash \
+1 -1
View File
@@ -17,4 +17,4 @@ mkdir polaris
tar -xzf $TARGET_FILE -C polaris
rm $TARGET_FILE
echo "polaris" >> $GITHUB_PATH
echo "version=$INPUT_VERSION" >> $GITHUB_OUTPUT
echo "::set-output name=version::$INPUT_VERSION"
-1
View File
@@ -1,4 +1,3 @@
This PR fixes #
## Checklist
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
build-int:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Setup polaris
uses: ./.github/actions/setup-polaris
with:
@@ -18,7 +18,7 @@ jobs:
build-ext:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Setup polaris
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
+98
View File
@@ -0,0 +1,98 @@
name: Triage issues
on:
schedule:
- cron: '0 16 * * Mon' # noon ET on Mondays
issues:
types:
- reopened
- opened
pull_request:
types:
- reopened
- opened
jobs:
notify:
if: github.actor!= 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- uses: octokit/request-action@v2.x
id: need_triage
with:
route: GET /repos/FairwindsOps/${{ github.event.repository.name }}/issues?labels=triage
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Scheduled Reminders
env:
TITLES: ${{ join(fromJSON(steps.need_triage.outputs.data).*.title, ';') }}
LINKS: ${{ join(fromJSON(steps.need_triage.outputs.data).*.html_url, ';') }}
run: |
echo "Scheduled reminder! ${TITLES}"
IFS=';' read -r -a titles <<< "$TITLES"
IFS=';' read -r -a links <<< "$LINKS"
message=""
for index in "${!links[@]}"; do
title=${titles[$index]}
link=${links[$index]}
echo "$index $title $link"
message="$message- <$link|$title>\\n"
done
echo "message: $message"
echo '{
"text": "Needs Triage",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "$message"
}
}
]
}' > body-template.json
export message=$(echo "${message}" | sed 's/"//g')
envsubst < body-template.json > body.json
cat body.json
curl -X POST "${{ secrets.SLACK_INCOMING_WEBHOOK }}" -H "Content-type: application/json" -d @./body.json
- name: Issue Notification
if: github.event.issue.title != ''
env:
TITLE: "${{ github.event.issue.title }}"
EVENT: github.event.pull_request.merged == true
LINK: "https://github.com/FairwindsOps/${{ github.event.repository.name }}/pulls/${{ github.event.issue.number }}"
run: |
echo '{
"text": "New Pull Request",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":issue: New Issue: <${LINK}|${TITLE}>"
}
}
]
}' > body-template.json
envsubst < body-template.json > body.json
curl -X POST "${{ secrets.SLACK_INCOMING_WEBHOOK }}" -H "Content-type: application/json" -d @./body.json
- name: PR Notification
if: github.event.pull_request.title != ''
env:
TITLE: "${{ github.event.pull_request.title }}"
LINK: "https://github.com/FairwindsOps/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }}"
run: |
echo '{
"text": "New Pull Request",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":pr: New Pull Request: <${LINK}|${TITLE}>"
}
}
]
}' > body-template.json
envsubst < body-template.json > body.json
curl -X POST "${{ secrets.SLACK_INCOMING_WEBHOOK }}" -H "Content-type: application/json" -d @./body.json
+1 -4
View File
@@ -1,7 +1,4 @@
# goreleaser is run via a wrapper that creates .goreleaser.yml from .goreleaser.yml.envsubst
.goreleaser.yml
# dist
# # Binaries for programs and plugins
# Binaries for programs and plugins
.go-version
*.exe
*.exe~
+51
View File
@@ -0,0 +1,51 @@
checksum:
name_template: 'checksums.txt'
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
builds:
- id: polaris
ldflags:
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
goarm:
- 6
- 7
archives:
- id: polaris
builds: ["polaris"]
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
signs:
- cmd: cosign
args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}"]
artifacts: checksum
release:
prerelease: auto
footer: |
You can verify the signature of the checksums.txt file using [cosign](https://github.com/sigstore/cosign).
```
cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub
```
brews:
- name: polaris
tap:
owner: FairwindsOps
name: homebrew-tap
folder: Formula
description: Open Source Best Practices for Kubernetes
test: |
system "#{bin}/polaris version"
-113
View File
@@ -1,113 +0,0 @@
checksum:
name_template: 'checksums.txt'
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
env:
- GOBIN={{ .Env.TMPDIR }}/go-bin
before:
hooks:
- go mod download
- ./scripts/install-and-run-packr2.sh
builds:
- id: polaris
ldflags:
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
goarm:
- 6
- 7
archives:
- id: polaris
builds: ["polaris"]
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
signs:
- cmd: cosign
args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}"]
artifacts: checksum
release:
# This is replaced using `envsubst`, depending on the git branch.
disable: ${skip_release}
prerelease: auto
footer: |
You can verify the signature of the checksums.txt file using [cosign](https://github.com/sigstore/cosign).
```
cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub
```
brews:
- name: polaris
# This is replaced using `envsubst`, depending on the git branch.
skip_upload: ${skip_release}
tap:
owner: FairwindsOps
name: homebrew-tap
folder: Formula
description: Open Source Best Practices for Kubernetes
test: |
system "#{bin}/polaris version"
dockers:
# There are multiple images to match the `--platform` docker build flag with
# combinations of `GOOS`, `GOARCH`, and `GOARM`
- image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
use: buildx
goarch: arm64
goos: linux
build_flag_templates:
- "--platform=linux/arm64"
docker_manifests:
# Create DOcker manifests that make multiple architectures available within a tag,
# and provide partial-version tags like 2, and 2.2.
- name_template: quay.io/fairwinds/polaris:{{ .FullCommit }}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Env.feature_docker_tag }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_feature_docker_tags}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:latest
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Tag }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Major }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Major }}.{{ .Minor }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
+1 -1
View File
@@ -1,2 +1,2 @@
## DO NOT EDIT - Managed by Terraform
* @rbren @mggude
* @rbren @makoscafee
+17 -11
View File
@@ -1,21 +1,27 @@
FROM alpine:3.18
FROM golang:1.17 AS build-env
WORKDIR /go/src/github.com/fairwindsops/polaris/
LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \
org.opencontainers.image.vendor="FairwindsOps, Inc." \
org.opencontainers.image.title="polaris" \
org.opencontainers.image.description="Polaris is a cli tool to help discover deprecated apiVersions in Kubernetes" \
org.opencontainers.image.documentation="https://polaris.docs.fairwinds.com/" \
org.opencontainers.image.source="https://github.com/FairwindsOps/polaris" \
org.opencontainers.image.url="https://github.com/FairwindsOps/polaris" \
org.opencontainers.image.licenses="Apache License 2.0"
ENV GO111MODULE=on
ENV GOPROXY=https://proxy.golang.org
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go get -u github.com/gobuffalo/packr/v2/packr2
COPY . .
RUN packr2 build -a -o polaris *.go
FROM alpine:3.16.1
WORKDIR /usr/local/bin
RUN apk -U upgrade
RUN apk --no-cache add ca-certificates
RUN addgroup -S polaris && adduser -u 1200 -S polaris -G polaris
USER 1200
COPY polaris .
COPY --from=build-env /go/src/github.com/fairwindsops/polaris/polaris .
WORKDIR /opt/app
+12 -10
View File
@@ -1,7 +1,7 @@
<div align="center" class="no-border">
<img src="https://polaris.docs.fairwinds.com/img/polaris-logo.png" alt="Polaris Logo">
<br>
<h3>Polaris is an open source policy engine for Kubernetes</h3>
<h3>Best Practices for Kubernetes Workload Configuration</h3>
<a href="https://github.com/FairwindsOps/polaris/releases">
<img src="https://img.shields.io/github/v/release/FairwindsOps/polaris">
</a>
@@ -16,12 +16,15 @@
</a>
</div>
Polaris is an open source policy engine for Kubernetes that validates and remediates resource configuration. It includes 30+ built in configuration policies, as well as the ability to build custom policies with JSON Schema. When run on the command line or as a mutating webhook, Polaris can automatically remediate issues based on policy criteria.
Fairwinds' Polaris keeps your clusters sailing smoothly. It runs a variety of checks to ensure that
Kubernetes pods and controllers are configured using best practices, helping you avoid
problems in the future.
Polaris can be run in three different modes:
* As a [dashboard](https://polaris.docs.fairwinds.com/dashboard) - Validate Kubernetes resources against policy-as-code.
* As an [admission controller](https://polaris.docs.fairwinds.com/admission-controller) - Automatically reject or modify workloads that don't adhere to your organization's policies.
* As a [command-line tool](https://polaris.docs.fairwinds.com/infrastructure-as-code) - Incorporate policy-as-code into the CI/CD process to test local YAML files.
* As a [dashboard](https://polaris.docs.fairwinds.com/dashboard), so you can audit what's running inside your cluster.
* As an [admission controller](https://polaris.docs.fairwinds.com/admission-controller), so you can automatically reject workloads that don't adhere to your organization's policies.
* As a [command-line tool](https://polaris.docs.fairwinds.com/infrastructure-as-code), so you can test local YAML files, e.g. as part of a CI/CD process.
<p align="center">
<img src="https://polaris.docs.fairwinds.com/img/architecture.svg" alt="Polaris Architecture" width="550"/>
</p>
@@ -38,9 +41,8 @@ and network with fellow Kubernetes users.
or
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
<a href="https://insights.fairwinds.com/auth/register/">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png"
alt="Love Fairwinds Open Source? Automate Fairwinds Open Source for free with Fairwinds Insights. Click to learn more" />
<a href="https://www.fairwinds.com/t-shirt-offer?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris-tshirt">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png" alt="Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt!" />
</a>
## Other Projects from Fairwinds
@@ -56,9 +58,9 @@ Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_
If you're interested in running Polaris in multiple clusters,
tracking the results over time, integrating with Slack, Datadog, and Jira,
or unlocking other functionality, check out
[Fairwinds Insights](https://fairwinds.com/pricing),
[Fairwinds Insights](https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris),
a platform for auditing and enforcing policy in Kubernetes clusters.
<a href="https://fairwinds.com/pricing">
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_Polaris_Ad.png" alt="Fairwinds Insights" />
</a>
-54
View File
@@ -1,54 +0,0 @@
successMessage: The ClusterRole does not allow pods/exec or pods/attach
failureMessage: The ClusterRole allows Pods/exec or pods/attach
category: Security
target: rbac.authorization.k8s.io/ClusterRole
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
required: ["metadata", "rules"]
anyOf:
# Do not alert on default ClusterRoles.
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
anyOf:
- const: 'admin'
- const: "cluster-admin"
- const: "edit"
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- properties:
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
anyOf:
- const: ""
- const: '*'
resources:
type: array
contains:
type: string
anyOf:
- const: '*'
- const: "pods/exec"
- const: "pods/attach"
verbs:
type: array
contains:
type: string
anyOf:
- const: '*'
# An exec is also possible by `get`ing a web socket.
- const: 'get'
- const: 'create'
@@ -1,88 +0,0 @@
successMessage: The ClusterRoleBinding does not reference the default cluster-admin ClusterRole or one with wildcard permissions
failureMessage: The ClusterRoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions
category: Security
target: rbac.authorization.k8s.io/ClusterRoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
anyOf:
# Do not alert on default ClusterRoleBindings.
- required: ["metadata"]
properties:
metadata:
type: object
required: ["name"]
properties:
name:
type: string
anyOf:
- const: "cluster-admin"
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- required: ["roleRef"]
properties:
roleRef:
required: ["apiGroup", "kind", "name"]
properties:
apiGroup:
type: string
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "ClusterRole"
name:
type: string
minLength: 1
not:
const: "cluster-admin"
additionalSchemaStrings:
rbac.authorization.k8s.io/ClusterRole: |
type: object
# Do not alert on default ClusterRoleBindings.
{{ if and (ne .metadata.name "cluster-admin") (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
required: ["metadata", "rules"]
allOf:
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
const: "{{ .roleRef.name }}"
- properties:
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
const: "*"
resources:
type: array
contains:
type: string
const: '*'
verbs:
type: array
uniqueItems: true
oneOf:
- contains:
type: string
const: '*'
- minItems: 7
items:
type: string
enum:
- "get"
- "list"
- "watch"
- "create"
- "update"
- "patch"
- "delete"
{{ end }}
@@ -1,82 +0,0 @@
successMessage: The ClusterRoleBinding does not reference a ClusterRole allowing pods/exec or pods/attach
failureMessage: The ClusterRoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist
category: Security
target: rbac.authorization.k8s.io/ClusterRoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
anyOf:
# Do not alert on default ClusterRoleBindings.
- required: ["metadata"]
properties:
metadata:
type: object
required: ["name"]
properties:
name:
type: string
anyOf:
- const: "cluster-admin"
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- required: ["roleRef"]
properties:
roleRef:
required: ["apiGroup", "kind", "name"]
properties:
apiGroup:
type: string
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "ClusterRole"
name:
type: string
minLength: 1
additionalSchemaStrings:
rbac.authorization.k8s.io/ClusterRole: |
type: object
# Do not alert on default ClusterRoleBindings.
{{ if and (ne .metadata.name "cluster-admin") (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
required: ["metadata", "rules"]
allOf:
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
const: "{{ .roleRef.name }}"
- properties:
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
anyOf:
- const: ""
- const: '*'
resources:
type: array
contains:
type: string
anyOf:
- const: '*'
- const: "pods/exec"
- const: "pods/attach"
verbs:
type: array
contains:
type: string
anyOf:
- const: '*'
# An exec is also possible by `get`ing a web socket.
- const: 'get'
- const: 'create'
{{ end }}
+2
View File
@@ -29,4 +29,6 @@ mutations:
- op: add
path: /resources/limits/cpu
value: 100m
comments:
- find: "cpu: 100m"
comment: "TODO: Set this to the maximum amount of CPU you want your workload to use"
+2
View File
@@ -29,4 +29,6 @@ mutations:
- op: add
path: /resources/requests/cpu
value: 100m
comments:
- find: "cpu: 100m"
comment: "TODO: Set this to the amount of CPU you want to reserve for your workload"
+1 -1
View File
@@ -28,4 +28,4 @@ schema:
mutations:
- op: remove
path: /securityContext/capabilities/add
path: /securityContext/capabilities
+1 -4
View File
@@ -12,7 +12,4 @@ schema:
items:
properties:
hostPort:
const: 0
mutations:
- op: remove
path: /ports/*/hostPort
const: 0
+3 -1
View File
@@ -55,6 +55,8 @@ schema:
- contains:
pattern: '^(?i)AUDIT_WRITE$'
mutations:
- op: replace
- op: remove
path: /securityContext/capabilities
- op: add
path: /securityContext/capabilities
value: {"drop": ["ALL"]}
+3 -1
View File
@@ -23,4 +23,6 @@ mutations:
- op: add
path: /livenessProbe
value: {"exec": { "command": [ "cat", "/tmp/healthy" ] }, "initialDelaySeconds": 5, "periodSeconds": 5 }
comment: "TODO: Change the livenessProbe setting to reflect your application's health"
comments:
- find: "livenessProbe:"
comment: "TODO: Change livenessProbe setting to reflect your health endpoints"
+2
View File
@@ -29,4 +29,6 @@ mutations:
- op: add
path: /resources/limits/memory
value: "512Mi"
comments:
- find: "memory: 512Mi"
comment: "TODO: Set this to the maximum amount of memory you want your workload to use"
+2
View File
@@ -29,4 +29,6 @@ mutations:
- op: add
path: /resources/requests/memory
value: "512Mi"
comments:
- find: "memory: 512Mi"
comment: "TODO: Set this to the amount of Memory you want to reserve for your workload"
+1 -6
View File
@@ -40,9 +40,4 @@ schema:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
mutations:
- op: add
path: /securityContext/allowPrivilegeEscalation
value: false
$ref: "#/definitions/goodSecurityContext"
+3 -1
View File
@@ -23,4 +23,6 @@ mutations:
- op: add
path: /readinessProbe
value: {"exec": { "command": [ "cat", "/tmp/healthy" ] }, "initialDelaySeconds": 5, "periodSeconds": 5 }
comment: "TODO: Change the readinessProbe setting to reflect your application's readiness to serve traffic"
comments:
- find: "readinessProbe:"
comment: "TODO: Change livenessProbe setting to reflect your health endpoints"
-56
View File
@@ -1,56 +0,0 @@
successMessage: The Role does not allow pods/exec or pods/attach
failureMessage: The Role allows Pods/exec or pods/attach
category: Security
target: rbac.authorization.k8s.io/Role
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
required: ["metadata", "rules"]
anyOf:
# Do not alert on default Roles.
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
anyOf:
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
anyOf:
- const: ""
- const: '*'
resources:
type: array
contains:
type: string
anyOf:
- const: '*'
- const: "pods/exec"
- const: "pods/attach"
verbs:
type: array
contains:
type: string
anyOf:
- const: '*'
# An exec is also possible by `get`ing a web socket.
- const: 'get'
- const: 'create'
@@ -1,98 +0,0 @@
successMessage: The RoleBinding does not reference the default cluster-admin ClusterRole or one with wildcard permissions
failureMessage: The RoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
anyOf:
# Pass RoleBindings that point to a Role.
- required: ["roleRef"]
properties:
roleRef:
required: ["kind"]
properties:
kind:
type: string
const: "Role"
# Do not alert on default ClusterRoleBindings.
- required: ["metadata"]
properties:
metadata:
type: object
required: ["name"]
properties:
name:
type: string
anyOf:
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- required: ["roleRef"]
properties:
roleRef:
required: ["apiGroup", "kind", "name"]
properties:
apiGroup:
type: string
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "ClusterRole"
name:
type: string
minLength: 1
not:
const: "cluster-admin"
additionalSchemaStrings:
rbac.authorization.k8s.io/ClusterRole: |
type: object
# This schema is validated for all roleBindings, regardless of their roleRef.
{{ if eq .roleRef.kind "ClusterRole" }}
{{ if and (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
required: ["metadata", "rules"]
allOf:
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
const: "{{ .roleRef.name }}"
- properties:
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
const: "*"
resources:
type: array
contains:
type: string
const: '*'
verbs:
type: array
uniqueItems: true
oneOf:
- contains:
type: string
const: '*'
- minItems: 7
items:
type: string
enum:
- "get"
- "list"
- "watch"
- "create"
- "update"
- "patch"
- "delete"
{{ end }}
{{ end }}
-96
View File
@@ -1,96 +0,0 @@
successMessage: The RoleBinding does not reference a Role with wildcard permissions
failureMessage: The RoleBinding references a Role with wildcard permissions
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
anyOf:
# Pass RoleBindings that point to a ClusterRole.
- required: ["roleRef"]
properties:
roleRef:
required: ["kind"]
properties:
kind:
type: string
const: "ClusterRole"
# Do not alert on default RoleBindings.
- required: ["metadata"]
properties:
metadata:
type: object
required: ["name"]
properties:
name:
type: string
anyOf:
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- required: ["roleRef"]
properties:
roleRef:
required: ["apiGroup", "kind", "name"]
properties:
apiGroup:
type: string
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "Role"
name:
type: string
minLength: 1
additionalSchemaStrings:
rbac.authorization.k8s.io/Role: |
type: object
# This schema is validated for all roleBindings, regardless of their roleRef.
{{ if eq .roleRef.kind "Role" }}
{{ if and (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
required: ["metadata", "rules"]
allOf:
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
const: "{{ .roleRef.name }}"
- properties:
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
const: "*"
resources:
type: array
contains:
type: string
const: '*'
verbs:
type: array
uniqueItems: true
oneOf:
- contains:
type: string
const: '*'
- minItems: 7
items:
type: string
enum:
- "get"
- "list"
- "watch"
- "create"
- "update"
- "patch"
- "delete"
{{ end }}
{{ end }}
@@ -1,92 +0,0 @@
successMessage: The RoleBinding does not reference a ClusterRole allowing pods/exec or pods/attach
failureMessage: The RoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
anyOf:
# Pass RoleBindings that point to a Role.
- required: ["roleRef"]
properties:
roleRef:
required: ["kind"]
properties:
kind:
type: string
const: "Role"
# Do not alert on default RoleBindings.
- required: ["metadata"]
properties:
metadata:
type: object
required: ["name"]
properties:
name:
type: string
anyOf:
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- required: ["roleRef"]
properties:
roleRef:
required: ["apiGroup", "kind", "name"]
properties:
apiGroup:
type: string
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "ClusterRole"
name:
type: string
minLength: 1
additionalSchemaStrings:
rbac.authorization.k8s.io/ClusterRole: |
type: object
# This schema is validated for all roleBindings, regardless of their roleRef.
{{ if eq .roleRef.kind "ClusterRole" }}
{{ if and (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
required: ["metadata", "rules"]
allOf:
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
const: "{{ .roleRef.name }}"
- properties:
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
anyOf:
- const: ""
- const: '*'
resources:
type: array
contains:
type: string
anyOf:
- const: '*'
- const: "pods/exec"
- const: "pods/attach"
verbs:
type: array
contains:
type: string
anyOf:
- const: '*'
# An exec is also possible by `get`ing a web socket.
- const: 'get'
- const: 'create'
{{ end }}
{{ end }}
-95
View File
@@ -1,95 +0,0 @@
successMessage: The RoleBinding does not reference a Role allowing Pod exec or attach
failureMessage: The RoleBinding references a Role that allows Pods/exec, allows pods/attach, or that does not exist
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
anyOf:
# Pass RoleBindings that point to a ClusterRole.
- required: ["roleRef"]
properties:
roleRef:
required: ["apiGroup", "kind", "name"]
properties:
apiGroup:
type: string
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "Role"
# Do not alert on default RoleBindings.
- required: ["metadata"]
properties:
metadata:
type: object
required: ["name"]
properties:
name:
type: string
anyOf:
- pattern: '^system:'
- const: "gce:podsecuritypolicy:calico-sa"
- required: ["roleRef"]
properties:
roleRef:
required: ["apiGroup", "kind", "name"]
properties:
apiGroup:
type: string
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "Role"
name:
type: string
minLength: 1
additionalSchemaStrings:
rbac.authorization.k8s.io/Role: |
type: object
# This schema is validated for all roleBindings, regardless of their roleRef.
{{ if eq .roleRef.kind "Role" }}
{{ if and (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
required: ["metadata", "rules"]
allOf:
- properties:
metadata:
required: ["name"]
properties:
name:
type: string
const: "{{ .roleRef.name }}"
- properties:
rules:
type: array
items:
type: object
not:
required: ["apiGroups", "resources", "verbs"]
properties:
apiGroups:
type: array
contains:
type: string
anyOf:
- const: ""
- const: '*'
resources:
type: array
contains:
type: string
anyOf:
- const: '*'
- const: "pods/exec"
- const: "pods/attach"
verbs:
type: array
contains:
type: string
anyOf:
- const: '*'
# An exec is also possible by `get`ing a web socket.
- const: 'get'
- const: 'create'
{{ end }}
{{ end }}
+31 -38
View File
@@ -10,41 +10,34 @@ schemaString: |
type: array
items:
type: object
oneOf:
- required: ["name", "value"]
properties:
name:
type: string
'$comment': These environment variable names will be disallowed.
allOf:
- not:
pattern: '(?i)^AWS_SECRET_ACCESS_KEY$'
- not:
pattern: '(?i)^GOOGLE_APPLICATION_CREDENTIALS$'
- not:
pattern: '(?i)^AZURE_.+KEY$'
- not:
pattern: '(?i)^OCI_CLI_KEY_CONTENT$'
- not:
pattern: '(?i)password'
- not:
pattern: '(?i)token'
- not:
pattern: '(?i)bearer'
- not:
pattern: '(?i)secret'
'$comment': This allows variable names not excluded above.
- pattern: '(?i).*'
value:
type: string
'$comment': These environment variable values will be disallowed.
allOf:
- not:
'$comment': THis matches variations like begin private key, begin rsa private key ...
pattern: '(?i)\s*-BEGIN\s+.*PRIVATE KEY-\s*'
- required: ["name", "valueFrom"]
properties:
name:
type: string
valueFrom:
type: object
required: ["name"]
properties:
name:
type: string
'$comment': These environment variable names will be disallowed.
allOf:
- not:
pattern: '(?i)^AWS_SECRET_ACCESS_KEY$'
- not:
pattern: '(?i)^GOOGLE_APPLICATION_CREDENTIALS$'
- not:
pattern: '(?i)^AZURE_.+KEY$'
- not:
pattern: '(?i)^OCI_CLI_KEY_CONTENT$'
- not:
pattern: '(?i)password'
- not:
pattern: '(?i)token'
- not:
pattern: '(?i)bearer'
- not:
pattern: '(?i)secret'
'$comment': This allows variable names not excluded above.
- pattern: '(?i).*'
value:
type: string
'$comment': These environment variable values will be disallowed.
allOf:
- not:
'$comment': THis matches variations like begin private key, begin rsa private key ...
pattern: '(?i)\s*-BEGIN\s+.*PRIVATE KEY-\s*'
-21
View File
@@ -1,21 +0,0 @@
successMessage: Pod has a valid topology spread constraint
failureMessage: Pod should be configured with a valid topology spread constraint
category: Reliability
target: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- topologySpreadConstraints
properties:
topologySpreadConstraints:
type: array
items:
type: object
properties:
topologyKey:
anyOf:
- type: string
const: "kubernetes.io/hostname"
- type: string
const: "topology.kubernetes.io/zone"
+6 -13
View File
@@ -17,10 +17,9 @@ package cmd
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
@@ -46,7 +45,6 @@ var (
helmValues string
checks []string
auditNamespace string
skipSslValidation bool
)
func init() {
@@ -65,7 +63,6 @@ func init() {
auditCmd.PersistentFlags().StringVar(&helmValues, "helm-values", "", "Optional flag to add helm values")
auditCmd.PersistentFlags().StringSliceVar(&checks, "checks", []string{}, "Optional flag to specify specific checks to check")
auditCmd.PersistentFlags().StringVar(&auditNamespace, "namespace", "", "Namespace to audit. Only applies to in-cluster audits")
auditCmd.PersistentFlags().BoolVar(&skipSslValidation, "skip-ssl-validation", false, "Skip https certificate verification")
}
var auditCmd = &cobra.Command{
@@ -140,13 +137,13 @@ func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
return "", err
}
dir, err := os.MkdirTemp("", "*")
dir, err := ioutil.TempDir("", "*")
if err != nil {
return "", err
}
params := []string{
"template", helmChart,
"--generate-name",
helmChart,
"--output-dir",
dir,
}
@@ -205,13 +202,9 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
} else {
req.Header.Set("Content-Type", "text/plain")
}
client := &http.Client{}
if skipSslValidation {
transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
client = &http.Client{Transport: transport}
}
resp, err := client.Do(req)
if err != nil {
logrus.Errorf("Error making request for output: %v", err)
os.Exit(1)
@@ -219,7 +212,7 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Errorf("Error reading response: %v", err)
@@ -230,7 +223,7 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
}
if outputFile != "" {
err := os.WriteFile(outputFile, outputBytes, 0644)
err := ioutil.WriteFile(outputFile, []byte(outputBytes), 0644)
if err != nil {
logrus.Errorf("Error writing output to file: %v", err)
os.Exit(1)
+58 -75
View File
@@ -15,7 +15,11 @@
package cmd
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -25,23 +29,19 @@ import (
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
yamlV3 "gopkg.in/yaml.v3"
"sigs.k8s.io/yaml"
)
const templateLineMarker = "# POLARIS_FIX_TMPL"
const templateOpenMarker = "POLARIS_OPEN_TMPL"
const templateCloseMarker = "POLARIS_CLOSE_TMPL"
var (
filesPath string
checksToFix []string
fixAll bool
isTemplate bool
)
func init() {
rootCmd.AddCommand(fixCommand)
fixCommand.PersistentFlags().StringVar(&filesPath, "files-path", "", "mutate and fix one or more YAML files in a specified folder")
fixCommand.PersistentFlags().BoolVar(&isTemplate, "template", false, "set to true when modifyng a YAML template, like a Helm chart (experimental)")
fixCommand.PersistentFlags().StringSliceVar(&checksToFix, "checks", []string{}, "Optional flag to specify specific checks to fix eg. checks=hostIPCSet,hostPIDSet and checks=all applies fix to all defined checks mutations")
}
@@ -53,7 +53,7 @@ var fixCommand = &cobra.Command{
logrus.Debug("Setting up controller manager")
if filesPath == "" {
logrus.Error("Please specify a files-path flag")
logrus.Error("Please specify a file-path flag")
cmd.Help()
os.Exit(1)
}
@@ -76,6 +76,8 @@ var fixCommand = &cobra.Command{
} else {
yamlFiles = append(yamlFiles, filesPath)
}
var contentStr string
isFirstResource := true
if len(checksToFix) > 0 {
if len(checksToFix) == 1 && checksToFix[0] == "all" {
@@ -84,58 +86,75 @@ var fixCommand = &cobra.Command{
allchecks = append(allchecks, key)
}
config.Mutations = allchecks
} else if len(checksToFix) == 0 && checksToFix[0] == "none" {
config.Mutations = nil
} else {
config.Mutations = checksToFix
}
}
for _, fullFilePath := range yamlFiles {
yamlContent, err := os.ReadFile(fullFilePath)
yamlFile, err := ioutil.ReadFile(fullFilePath)
if err != nil {
logrus.Fatalf("Error reading file with file path %s: %v", fullFilePath, err)
}
if err != nil {
logrus.Fatalf("Error marshalling %s: %v", fullFilePath, err)
}
dec := yamlV3.NewDecoder(bytes.NewReader(yamlFile))
if isTemplate {
yamlContent = []byte(detemplate(string(yamlContent)))
}
kubeResources := kube.CreateResourceProviderFromYaml(string(yamlContent))
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
if err != nil {
logrus.Fatalf("Error applying schema check to the resources %s: %v", fullFilePath, err)
}
allMutations := mutation.GetMutationsFromResults(results)
updatedYamlContent := ""
if len(allMutations) > 0 {
for _, resources := range kubeResources.Resources {
for _, resource := range resources {
key := fmt.Sprintf("%s/%s/%s", resource.Kind, resource.Resource.GetName(), resource.Resource.GetNamespace())
for {
data := map[string]interface{}{}
err := dec.Decode(&data)
// check it was parsed
if data == nil {
continue
}
// break the loop in case of EOF
if errors.Is(err, io.EOF) {
break
}
if err != nil {
logrus.Fatalf("Error decoding data for file with file path %s: %v", fullFilePath, err)
}
yamlContent, err := yamlV3.Marshal(data)
if err != nil {
logrus.Fatalf("Error marshalling %s: %v", fullFilePath, err)
}
kubeResources := kube.CreateResourceProviderFromYaml(string(yamlContent))
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
if err != nil {
logrus.Fatalf("Error applying schema check to the resources %s: %v", fullFilePath, err)
}
comments, allMutations := mutation.GetMutationsAndCommentsFromResults(results)
updatedYamlContent := string(yamlContent)
if len(allMutations) > 0 {
for _, resources := range kubeResources.Resources {
key := fmt.Sprintf("%s/%s/%s", resources[0].Kind, resources[0].Resource.GetName(), resources[0].Resource.GetNamespace())
mutations := allMutations[key]
mutatedYamlContent, err := mutation.ApplyAllMutations(string(resource.OriginalObjectYAML), mutations)
mutated, err := mutation.ApplyAllSchemaMutations(&config, kubeResources, resources[0], mutations)
if err != nil {
logrus.Errorf("Error applying schema mutations to the resource %s: %v", key, err)
logrus.Errorf("Error applying schema mutations to the resources: %v", err)
os.Exit(1)
}
if updatedYamlContent != "" {
updatedYamlContent += "\n---\n"
mutatedYamlContent, err := yaml.JSONToYAML(mutated.OriginalObjectJSON)
if err != nil {
logrus.Errorf("Error converting JSON to Yaml : %v", err)
os.Exit(1)
}
updatedYamlContent += mutatedYamlContent
updatedYamlContent = mutation.UpdateMutatedContentWithComments(string(mutatedYamlContent), comments)
}
}
if isFirstResource {
contentStr = updatedYamlContent
isFirstResource = false
} else {
contentStr += "\n"
contentStr += "---"
contentStr += "\n"
contentStr += updatedYamlContent
}
}
if isTemplate {
updatedYamlContent = retemplate(updatedYamlContent)
}
if updatedYamlContent != "" {
err = os.WriteFile(fullFilePath, []byte(updatedYamlContent), 0644)
if contentStr != "" {
err = ioutil.WriteFile(fullFilePath, []byte(contentStr), 0644)
if err != nil {
logrus.Fatalf("Error writing output to file: %v", err)
}
@@ -145,42 +164,6 @@ var fixCommand = &cobra.Command{
},
}
func detemplate(content string) string {
lines := strings.Split(content, "\n")
for idx, line := range lines {
lines[idx] = detemplateLine(line)
}
return strings.Join(lines, "\n")
}
func retemplate(content string) string {
lines := strings.Split(content, "\n")
for idx, line := range lines {
lines[idx] = retemplateLine(line)
}
return strings.Join(lines, "\n")
}
func detemplateLine(line string) string {
if !strings.HasPrefix(strings.TrimSpace(line), "{{") {
line = strings.ReplaceAll(line, "{", templateOpenMarker)
line = strings.ReplaceAll(line, "}", templateCloseMarker)
return line
}
tmplStart := strings.Index(line, "{{")
newLine := line[:tmplStart] + templateLineMarker + line[tmplStart:]
return newLine
}
func retemplateLine(line string) string {
if !strings.Contains(line, templateLineMarker) {
line = strings.ReplaceAll(line, templateOpenMarker, "{")
line = strings.ReplaceAll(line, templateCloseMarker, "}")
return line
}
return strings.Replace(line, templateLineMarker, "", 1)
}
func getYamlFiles(rootpath string) ([]string, error) {
var list []string
err := filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
+5 -4
View File
@@ -15,11 +15,13 @@
package cmd
import (
"flag"
"os"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var configPath string
@@ -40,7 +42,9 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any configured exemption.")
rootCmd.PersistentFlags().BoolVarP(&disallowConfigExemptions, "disallow-config-exemptions", "", false, "Disallow exemptions set within the configuration file.")
rootCmd.PersistentFlags().BoolVarP(&disallowAnnotationExemptions, "disallow-annotation-exemptions", "", false, "Disallow any exemption defined as a controller annotation.")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level to be output (trace, debug, info, warning, error, fatal, panic).")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level.")
flag.Parse()
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
}
var config conf.Configuration
@@ -76,9 +80,6 @@ var rootCmd = &cobra.Command{
}
os.Exit(1)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
os.Stderr.WriteString("\n\nWant more? Automate Polaris for free with Fairwinds Insights!\n🚀 https://fairwinds.com/insights-signup/polaris 🚀 \n")
},
}
// Execute the stuff
-3
View File
@@ -31,7 +31,4 @@ var versionCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Polaris version:" + version)
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
+4 -4
View File
@@ -16,6 +16,7 @@ package cmd
import (
"os"
"time"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -30,7 +31,6 @@ var webhookPort int
var disableWebhookConfigInstaller bool
var enableMutations bool
var enableValidations bool
var certDir string
func init() {
rootCmd.AddCommand(webhookCmd)
@@ -38,7 +38,6 @@ func init() {
webhookCmd.PersistentFlags().BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false, "Disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.")
webhookCmd.PersistentFlags().BoolVar(&enableValidations, "validate", true, "Enable the validating webhook to reject workloads with issues")
webhookCmd.PersistentFlags().BoolVar(&enableMutations, "mutate", false, "Enable the mutating webhook to modify workloads with issues")
webhookCmd.PersistentFlags().StringVar(&certDir, "cert-dir", "/opt/cert", "Directory in which tls certificate is located")
}
var webhookCmd = &cobra.Command{
@@ -49,7 +48,7 @@ var webhookCmd = &cobra.Command{
logrus.Debug("Setting up controller manager")
mgr, err := manager.New(k8sConfig.GetConfigOrDie(), manager.Options{
CertDir: certDir,
CertDir: "/opt/cert",
Port: webhookPort,
})
if err != nil {
@@ -57,8 +56,9 @@ var webhookCmd = &cobra.Command{
os.Exit(1)
}
_, err = os.Stat(certDir + "/tls.crt")
_, err = os.Stat("/opt/cert/tls.crt")
if os.IsNotExist(err) {
time.Sleep(time.Second * 10)
panic("Cert does not exist")
}
server := mgr.GetWebhookServer()
+1 -22
View File
@@ -55,28 +55,7 @@ the deployment.
To enable the mutating webhook, add `--set webhook.mutate=true` to your
Helm instlallation command.
The following default checks currently have mutation support enabled:
* `hostPIDSet`
* `hostNetworkSet`
* `hostIPCSet`
* `priorityClassNotSet`
* `hostPortSet`
* `pullPolicyNotAlways`
* `deploymentMissingReplicas`
* `dangerousCapabilities`
* `cpuLimitsMissing`
* `memoryLimitsMissing`
* `livenessProbeMissing`
* `memoryRequestsMissing`
* `cpuRequestsMissing`
* `runAsPrivileged`
* `readinessProbeMissing`
* `privilegeEscalationAllowed`
* `notReadOnlyRootFilesystem`
* `insecureCapabilities`
* `runAsRootAllowed`
If you'd like to
By default, the only mutation enabled is `pullPolicyNotAlways`. If you'd like to
enable other mutations, you can set the `webhook.mutations` flag.
-24
View File
@@ -5,30 +5,6 @@ meta:
content: "Fairwinds Polaris | Changelog"
---
## 8.0.0
* Change default severity from `ignore` to `warning` for `priorityClassNotSet`, `metadataAndNameMismatched`, `missingPodDisruptionBudget`, `automountServiceAccountToken`, `missingNetworkPolicy` checks.
* Change default severity from `warning` to `danger` for `sensitiveContainerEnvVar`, `sensitiveConfigmapContent`, `clusterrolePodExecAttach`, `rolePodExecAttach`, `clusterrolebindingPodExecAttach`, `rolebindingClusterRolePodExecAttach`, `rolebindingRolePodExecAttach`,`clusterrolebindingClusterAdmin`,`rolebindingClusterAdminClusterRole`,`rolebindingClusterAdminRole` checks.
## 7.4.0
* Skip https certificate verification (#920)
## 7.3.0
* Add a check for `topologySpreadConstraint` (#879)
## 7.2.0
* Enable new RBAC / sensitive content / Pod exec checks, add `hasPrefix` and `hasSuffix` functions to the GO template, exempt `system:` name prefixes for RBAC checks, sensitive content checks ignore `valueFrom`, (#832)
## 7.1.0
* Let Polaris modify YAML without losing comments/formatting (#821)
* Add checks for RBAC allowing exec or attaching to a Pod (#820)
* Add `clusterrolebindingClusterAdmin`, `rolebindingClusterAdminRole`, and `rolebindingClusterAdminClusterRole` checks + schema tests (#823)
## 7.0.2
* Fixes for pretty CLI output
* Some new checks (disabled by default)
* Some additional features in templating engine
## 7.0.1
* Documentation updates
+2 -36
View File
@@ -14,55 +14,22 @@ key | default | description
`livenessProbeMissing` | `warning` | Fails when a liveness probe is not configured for a pod.
`tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
`priorityClassNotSet` | `warning` | Fails when a priorityClassName is not set for a pod.
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
`deploymentMissingReplicas` | `warning` | Fails when there is only one replica for a deployment.
`missingPodDisruptionBudget` | `warning`
`metadataAndNameMismatched` | `warning`
`topologySpreadConstraint` | `warning` | Fails when there is no topology spread constraint on the pod
`missingPodDisruptionBudget` | `ignore`
## Background
### Liveness and Readiness Probes
Readiness and liveness probes can help maintain the health of applications running inside Kubernetes. By default, Kubernetes only knows whether or not a process is running, not if it's healthy. Properly configured readiness and liveness probes will also be able to ensure the health of an application.
Readiness probes are designed to ensure that an application has reached a "ready" state. In many cases there is a period of time between when a webserver process starts and when it is ready to receive traffic. A readiness probe can ensure the traffic is not sent to a pod until it is actually ready to receive traffic.
Liveness probes are designed to ensure that an application stays in a healthy state. When a liveness probe fails, the pod will be restarted.
### Image Pull Policy
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
### Topology Spread Constraints
By default, the Kubernetes scheduler uses a bin-packing algorithm to fit as many pods as possible into a cluster. The scheduler prefers a more evenly distributed general node load to app replicas precisely spread across nodes. Therefore, by default, multi-replica is not guaranteed to be spread across multiple availability zones. Kubernetes provides topologySpreadConstraint configuration in order to better ensure pod spread across multiple AZs and/or Hosts.
Example of a topologySpreadConstraint spreading across zones:
```
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-basic-demo
spec:
selector:
matchLabels:
app.kubernetes.io/name: basic-demo
app.kubernetes.io/instance: demo
template:
metadata:
labels:
app.kubernetes.io/name: basic-demo
app.kubernetes.io/instance: demo
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: ScheduleAnyway
```
## Further Reading
- [What's Wrong With The Docker :latest Tag?](https://vsupalov.com/docker-latest-tag/)
@@ -70,4 +37,3 @@ spec:
- [Kubernetes Docs: Configure Liveness and Readiness Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)
- [Utilizing Kubernetes Liveness and Readiness Probes to Automatically Recover From Failure](https://medium.com/spire-labs/utilizing-kubernetes-liveness-and-readiness-probes-to-automatically-recover-from-failure-2fe0314f2b2e)
- [Kubernetes Liveness and Readiness Probes: How to Avoid Shooting Yourself in the Foot](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/)
- [Topology Spread Cosntraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/)
-13
View File
@@ -11,10 +11,8 @@ for privilege escalation.
key | default | description
----|---------|------------
`automountServiceAccountToken` | `warning` | Fails when `automountServiceAccountToken` is automounted.
`hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
`hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
`linuxHardening` | `danger` | Fails when neither `AppArmor`, `Seccomp`, `SELinux`, or dropping Linux Capabilities is in use.
`notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
`privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
`runAsRootAllowed` | `warning` | Fails when `securityContext.runAsNonRoot` is not true.
@@ -24,17 +22,6 @@ key | default | description
`hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
`hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
`tlsSettingsMissing` | `warning` | Fails when an Ingress lacks TLS settings.
`sensitiveContainerEnvVar` | `danger` | Fails when the container sets potentially sensitive environment variables.
`sensitiveConfigmapContent` | `danger` | Fails when potentially sensitive content is detected in the ConfigMap keys or values.
`missingNetworkPolicy` | `warning`
`clusterrolePodExecAttach` | `danger` | Fails when the ClusterRole allows Pods/exec or pods/attach.
`rolePodExecAttach` | `danger` | Fails when the Role allows Pods/exec or pods/attach.
`clusterrolebindingPodExecAttach` | `danger` | Fails when the ClusterRoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist.
`rolebindingRolePodExecAttach` | `danger` | Fails when the RoleBinding references a Role that allows Pods/exec, allows pods/attach, or that does not exist.
`rolebindingClusterRolePodExecAttach` | `danger` | Fails when the RoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist.
`clusterrolebindingClusterAdmin` | `danger` | Fails when the ClusterRoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions.
`rolebindingClusterAdminClusterRole` | `danger` | Fails when the RoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions.
`rolebindingClusterAdminRole` | `danger` | Fails when the RoleBinding references a Role with wildcard permissions.
## Background
-12
View File
@@ -167,18 +167,6 @@ schemaString: |
{{ end }}
```
### Additional Go Template Functions
These functions are also available in the GO template.
* [hasPrefix](https://pkg.go.dev/strings#HasPrefix) - for example, `hasPrefix "string" "prefix"`
* [hasSuffix](https://pkg.go.dev/strings#HasSuffix) - for example, `hasSuffix "string" "suffix"`
For example, the `hasPrefix` function can be used in a template to determine whether a resource name starts with `system:`
```
{{ if hasPrefix .metadata.name "system:" }}
```
## Multi-Resource Checks
You can write checks that span multiple resources. This is helpful for ensuring e.g.
that every Deployment has a PDB or an HPA associated with it.
+3618 -5345
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -8,9 +8,9 @@
},
"description": "A repository with a Vuepress template for Fairwinds projects",
"devDependencies": {
"vuepress": "^1.9.7",
"vuepress": "^1.4.0",
"vuepress-plugin-clean-urls": "^1.1.1",
"vuepress-plugin-redirect": "^1.2.5"
"vuepress-plugin-redirect": "^1.2.3"
},
"directories": {
"doc": "docs"
+1 -15
View File
@@ -6,17 +6,11 @@ checks:
pullPolicyNotAlways: warning
readinessProbeMissing: warning
livenessProbeMissing: warning
topologySpreadConstraint: warning
pdbDisruptionsIsZero: warning
missingPodDisruptionBudget: warning
metadataAndNameMismatched: warning
# efficiency
cpuRequestsMissing: warning
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
# security
automountServiceAccountToken: warning
hostIPCSet: danger
@@ -31,21 +25,13 @@ checks:
insecureCapabilities: warning
hostNetworkSet: danger
hostPortSet: warning
tlsSettingsMissing: warning
sensitiveContainerEnvVar: danger
sensitiveConfigmapContent: danger
clusterrolePodExecAttach: danger
rolePodExecAttach: danger
clusterrolebindingPodExecAttach: danger
rolebindingClusterRolePodExecAttach: danger
rolebindingRolePodExecAttach: danger
clusterrolebindingClusterAdmin: danger
rolebindingClusterAdminClusterRole: danger
rolebindingClusterAdminRole: danger
# custom
resourceLimits: warning
imageRegistry: danger
exemptions:
- controllerNames:
- my-network-controller
+3 -109
View File
@@ -1,28 +1,23 @@
checks:
# reliability
deploymentMissingReplicas: warning
priorityClassNotSet: warning
priorityClassNotSet: ignore
tagNotSpecified: danger
pullPolicyNotAlways: warning
readinessProbeMissing: warning
livenessProbeMissing: warning
metadataAndNameMismatched: warning
metadataAndNameMismatched: ignore
pdbDisruptionsIsZero: warning
missingPodDisruptionBudget: warning
topologySpreadConstraint: warning
missingPodDisruptionBudget: ignore
# efficiency
cpuRequestsMissing: warning
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
# security
automountServiceAccountToken: warning
hostIPCSet: danger
hostPIDSet: danger
linuxHardening: warning
missingNetworkPolicy: warning
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: danger
@@ -32,61 +27,11 @@ checks:
hostNetworkSet: danger
hostPortSet: warning
tlsSettingsMissing: warning
sensitiveContainerEnvVar: danger
sensitiveConfigmapContent: danger
clusterrolePodExecAttach: danger
rolePodExecAttach: danger
clusterrolebindingPodExecAttach: danger
rolebindingClusterRolePodExecAttach: danger
rolebindingRolePodExecAttach: danger
clusterrolebindingClusterAdmin: danger
rolebindingClusterAdminClusterRole: danger
rolebindingClusterAdminRole: danger
mutations:
- pullPolicyNotAlways
exemptions:
- namespace: kube-system
controllerNames:
- dns-controller
- ebs-csi-controller
- ebs-csi-node
- kindnet
- kops-controller
- kube-dns
- kube-flannel-ds
- kube-proxy
- kube-scheduler
- vpa-recommender
rules:
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- namespace: kube-system
controllerNames:
- coredns
rules:
- automountServiceAccountToken
- missingNetworkPolicy
- namespace: kube-system
controllerNames:
- ebs-csi-controller
rules:
- sensitiveContainerEnvVar
- namespace: kube-system
controllerNames:
- coredns-autoscaler
rules:
- linuxHardening
- namespace: local-path-storage
controllerNames:
- local-path-provisioner
rules:
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- namespace: kube-system
controllerNames:
- kube-apiserver
@@ -109,48 +54,7 @@ exemptions:
- runAsPrivileged
- notReadOnlyRootFilesystem
- hostPIDSet
- namespace: datadog
controllerNames:
- datadogtoken
rules:
- sensitiveConfigmapContent
- namespace: datadog
controllerNames:
- datadog-cluster-agent-apiserver
rules:
- rolebindingClusterAdminRole
- rolebindingRolePodExecAttach
- controllerNames:
- ingress-nginx-controller
rules:
- sensitiveConfigmapContent
- controllerNames:
- ingress-nginx-controller
- ingress-nginx-default-backend
- polaris
- rbac-manager
rules:
- automountServiceAccountToken
- missingNetworkPolicy
- controllerNames:
- aws-iam-authenticator
- aws-load-balancer-controller
- docker-registry
- external-dns
- kube2iam
- metrics-server
rules:
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- controllerNames:
- oauth2-proxy
rules:
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- sensitiveContainerEnvVar
- controllerNames:
- kube-flannel-ds
rules:
@@ -168,9 +72,6 @@ exemptions:
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- controllerNames:
- cluster-autoscaler
@@ -178,9 +79,6 @@ exemptions:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- readinessProbeMissing
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- controllerNames:
- vpa
@@ -197,10 +95,6 @@ exemptions:
- readinessProbeMissing
- livenessProbeMissing
- notReadOnlyRootFilesystem
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- sensitiveContainerEnvVar
- controllerNames:
- nginx-ingress-controller
+53 -46
View File
@@ -1,55 +1,60 @@
module github.com/fairwindsops/polaris
go 1.19
go 1.17
require (
github.com/fatih/color v1.14.1
github.com/evanphx/json-patch/v5 v5.6.0
github.com/fatih/color v1.13.0
github.com/gobuffalo/packr/v2 v2.8.3
github.com/gorilla/mux v1.8.0
github.com/qri-io/jsonschema v0.1.1
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.1
github.com/thoas/go-funk v0.9.3
golang.org/x/text v0.7.0 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0
github.com/thoas/go-funk v0.9.2
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.26.1
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
sigs.k8s.io/controller-runtime v0.14.4
k8s.io/api v0.24.3
k8s.io/apimachinery v0.24.3
k8s.io/client-go v0.24.1
sigs.k8s.io/controller-runtime v0.12.1
sigs.k8s.io/yaml v1.3.0
)
require (
github.com/pkg/errors v0.9.1
gomodules.xyz/jsonpatch/v2 v2.2.0
)
require gomodules.xyz/jsonpatch/v2 v2.2.0
require (
cloud.google.com/go/compute v1.6.1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.20 // 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/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gobuffalo/logger v1.0.7 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/gobuffalo/logger v1.0.6 // indirect
github.com/gobuffalo/packd v1.0.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/karrick/godirwalk v1.17.0 // indirect
@@ -57,30 +62,32 @@ require (
github.com/markbates/errx v1.1.0 // indirect
github.com/markbates/oncer v1.0.0 // indirect
github.com/markbates/safe v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // 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/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.34.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/component-base v0.26.1 // indirect
k8s.io/klog/v2 v2.90.0 // indirect
k8s.io/kube-openapi v0.0.0-20230202010329-39b3636cbaa3 // indirect
k8s.io/utils v0.0.0-20230202215443-34013725500c // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
k8s.io/component-base v0.24.1 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
)
+513 -96
View File
File diff suppressed because it is too large Load Diff
-9
View File
@@ -33,7 +33,6 @@ var (
"hostPIDSet",
"hostNetworkSet",
"automountServiceAccountToken",
"topologySpreadConstraint",
// Container checks
"memoryLimitsMissing",
"memoryRequestsMissing",
@@ -60,14 +59,6 @@ var (
"missingPodDisruptionBudget",
"missingNetworkPolicy",
"sensitiveConfigmapContent",
"clusterrolePodExecAttach",
"rolePodExecAttach",
"clusterrolebindingPodExecAttach",
"rolebindingClusterRolePodExecAttach",
"rolebindingRolePodExecAttach",
"clusterrolebindingClusterAdmin",
"rolebindingClusterAdminClusterRole",
"rolebindingClusterAdminRole",
}
)
+3 -3
View File
@@ -19,8 +19,8 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/gobuffalo/packr/v2"
@@ -70,10 +70,10 @@ func ParseFile(path string) (Configuration, error) {
if err2 != nil {
return Configuration{}, err2
}
rawBytes, err = io.ReadAll(response.Body)
rawBytes, err = ioutil.ReadAll(response.Body)
} else {
// path is local
rawBytes, err = os.ReadFile(path)
rawBytes, err = ioutil.ReadFile(path)
}
if err != nil {
return Configuration{}, err
+12 -16
View File
@@ -25,6 +25,7 @@ import (
"github.com/qri-io/jsonschema"
"github.com/thoas/go-funk"
"gomodules.xyz/jsonpatch/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
@@ -52,12 +53,10 @@ var HandledTargets = []TargetKind{
TargetPodTemplate,
}
// Mutation defines how to change a YAML file, in the style of JSON Patch
type Mutation struct {
Path string
Op string
Value interface{}
Comment string
// MutationComment is the comments added to a mutated file
type MutationComment struct {
Find string `yaml:"find" json:"find"`
Comment string `yaml:"comment" json:"comment"`
}
// SchemaCheck is a Polaris check that runs using JSON Schema
@@ -76,14 +75,14 @@ type SchemaCheck struct {
AdditionalSchemas map[string]map[string]interface{} `yaml:"additionalSchemas" json:"additionalSchemas"`
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
AdditionalValidators map[string]jsonschema.RootSchema `yaml:"-" json:"-"`
Mutations []Mutation `yaml:"mutations" json:"mutations"`
Mutations []jsonpatch.Operation `yaml:"mutations" json:"mutations"`
Comments []MutationComment `yaml:"comments" json:"comments"`
}
type resourceMinimum string
type resourceMaximum string
// UnmarshalYAMLOrJSON is a helper function to unmarshal data in an arbitrary format
func UnmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
func unmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
reader := bytes.NewReader(raw)
d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
@@ -100,7 +99,7 @@ func UnmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
// ParseCheck parses a check from a byte array
func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
check := SchemaCheck{}
err := UnmarshalYAMLOrJSON(rawBytes, &check)
err := unmarshalYAMLOrJSON(rawBytes, &check)
if err != nil {
return check, err
}
@@ -223,10 +222,7 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
newCheck.AdditionalSchemaStrings = map[string]string{}
for kind, tmplString := range templateStrings {
tmpl := template.New(newCheck.ID).Funcs(template.FuncMap{
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
})
tmpl := template.New(newCheck.ID)
tmpl, err := tmpl.Parse(tmplString)
if err != nil {
return nil, err
@@ -247,13 +243,13 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
val := jsonschema.RootSchema{}
err := UnmarshalYAMLOrJSON([]byte(schemaStr), &val)
err := unmarshalYAMLOrJSON([]byte(schemaStr), &val)
if err != nil {
return nil, err
}
newCheck.AdditionalValidators[kind] = val
}
err := UnmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
err := unmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
if err != nil {
return nil, err
}
-5
View File
@@ -204,14 +204,12 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path != basePath {
logrus.Warningf("Path not found: %s", r.URL.Path)
http.NotFound(w, r)
return
}
adjustedConf := getConfigForQuery(c, r.URL.Query())
if auditData == nil {
logrus.Infof("Creating resource provider")
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
@@ -219,7 +217,6 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
return
}
logrus.Infof("Running audit")
var auditData validator.AuditData
auditData, err = validator.RunAudit(adjustedConf, k)
if err != nil {
@@ -227,10 +224,8 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
http.Error(w, "Error running audit", 500)
return
}
logrus.Infof("Sending results")
MainHandler(w, r, adjustedConf, auditData, basePath)
} else {
logrus.Infof("Sending results")
MainHandler(w, r, adjustedConf, *auditData, basePath)
}
-1
View File
@@ -5,7 +5,6 @@
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">
<img class="fw-logo" src="static/images/white_logo_fairwinds.svg" alt="Fairwinds" />
</a>
<div style="color: white;"> Want more? Automate Polaris with <a href="https://www.fairwinds.com/insights-signup/polaris"><strong>Fairwinds Insights</strong></a></div>
<div class="right-section p-0 d-flex justify-content-between">
<a href="https://github.com/FairwindsOps" target="_blank">
<img class="gh-logo" src="static/images/white_icon_github.svg" alt="Github" />
+10 -37
View File
@@ -20,7 +20,6 @@ import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/thoas/go-funk"
"gopkg.in/yaml.v3"
kubeAPICoreV1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
@@ -30,9 +29,6 @@ import (
"k8s.io/client-go/dynamic"
)
// There are often many ReplicaSets that are orphaned (default 10 per deployment) so we don't cache all of them
var skipListCacheKinds = []string{"ReplicaSet", "Job"}
// GenericResource is a base implementation with some free methods for inherited structs
type GenericResource struct {
Kind string
@@ -41,7 +37,6 @@ type GenericResource struct {
PodSpec *kubeAPICoreV1.PodSpec
PodTemplate interface{}
OriginalObjectJSON []byte
OriginalObjectYAML []byte
}
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
@@ -133,13 +128,11 @@ func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
if err != nil {
return GenericResource{}, err
}
res, err := NewGenericResourceFromUnstructured(unst, nil)
res.OriginalObjectYAML = contentBytes
return res, err
return NewGenericResourceFromUnstructured(unst, nil)
}
// ResolveControllerFromPod builds a new workload for a given Pod
func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
workload, err := resolveControllerFromPod(ctx, podResource, dynamicClient, restMapper, objectCache)
if err != nil {
return workload, err
@@ -150,7 +143,7 @@ func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
return workload, err
}
func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
podWorkload, err := NewGenericResourceFromPod(podResource, nil)
if err != nil {
return podWorkload, err
@@ -174,12 +167,7 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
lastKey = key
abstractObject, ok := objectCache[key]
if !ok {
var err error
if funk.Contains(skipListCacheKinds, firstOwner.Kind) {
err = cacheSingleObject(ctx, firstOwner.APIVersion, firstOwner.Kind, topMeta.GetNamespace(), firstOwner.Name, dynamicClient, restMapper, objectCache)
} else {
err = cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
}
err := cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
if err != nil {
logrus.Warnf("Error caching objects of Kind %s %v", firstOwner.Kind, err)
break
@@ -217,21 +205,7 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
return workload, nil
}
func cacheSingleObject(ctx context.Context, apiVersion, kind, namespace, name string, dynamicClient dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
logrus.Debugf("Caching a single %s", kind)
object, err := getObject(ctx, namespace, kind, apiVersion, name, dynamicClient, restMapper)
if err != nil {
logrus.Warnf("Error retrieving object %s/%s/%s/%s because of error: %v", kind, apiVersion, namespace, name, err)
return err
}
key := fmt.Sprintf("%s/%s/%s", object.GetKind(), object.GetNamespace(), object.GetName())
logrus.Debugf("Caching key %s", key)
objectCache[key] = *object
return nil
}
func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamicClient dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
logrus.Debugf("Caching all %s", kind)
func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
fqKind := schema.FromAPIVersionAndKind(apiVersion, kind)
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
if err != nil {
@@ -239,26 +213,25 @@ func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamic
return err
}
objects, err := dynamicClient.Resource(mapping.Resource).Namespace("").List(ctx, kubeAPIMetaV1.ListOptions{})
objects, err := (*dynamicClient).Resource(mapping.Resource).Namespace("").List(ctx, kubeAPIMetaV1.ListOptions{})
if err != nil {
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
return err
}
for idx, object := range objects.Items {
key := fmt.Sprintf("%s/%s/%s", object.GetKind(), object.GetNamespace(), object.GetName())
logrus.Debugf(" caching key %s", key)
objectCache[key] = objects.Items[idx]
}
return nil
}
func getObject(ctx context.Context, namespace, kind, version, name string, dynamicClient dynamic.Interface, restMapper *meta.RESTMapper) (*unstructured.Unstructured, error) {
fqKind := schema.FromAPIVersionAndKind(version, kind)
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
func getObject(ctx context.Context, namespace, kind, version, name string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper) (*unstructured.Unstructured, error) {
fqKind := schema.ParseGroupKind(kind)
mapping, err := (*restMapper).RESTMapping(fqKind, version)
if err != nil {
return nil, err
}
object, err := dynamicClient.Resource(mapping.Resource).Namespace(namespace).Get(ctx, name, kubeAPIMetaV1.GetOptions{})
object, err := (*dynamicClient).Resource(mapping.Resource).Namespace(namespace).Get(ctx, name, kubeAPIMetaV1.GetOptions{})
return object, err
}
+14 -22
View File
@@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
@@ -182,7 +183,7 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
return nil, err
}
restMapper := restmapper.NewDiscoveryRESTMapper(groupResources)
obj, err := getObject(ctx, namespace, kind, version, name, dynamicInterface, &restMapper)
obj, err := getObject(ctx, namespace, kind, version, name, &dynamicInterface, &restMapper)
if err != nil {
logrus.Errorf("Could not find workload %s: %v", workload, err)
return nil, err
@@ -215,7 +216,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
if !strings.HasSuffix(path, ".yml") && !strings.HasSuffix(path, ".yaml") {
return nil
}
contents, err := os.ReadFile(path)
contents, err := ioutil.ReadFile(path)
if err != nil {
logrus.Errorf("Error reading file: %v", path)
return err
@@ -258,11 +259,11 @@ func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration
logrus.Errorf("Error connecting to dynamic interface: %v", err)
return nil, err
}
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, dynamicInterface, c)
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface, c)
}
// CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic dynamic.Interface, c conf.Configuration) (*ResourceProvider, error) {
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface, c conf.Configuration) (*ResourceProvider, error) {
listOpts := metav1.ListOptions{}
serverVersion, err := kube.Discovery().ServerVersion()
if err != nil {
@@ -277,14 +278,12 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
}
provider := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, sourceType, clusterName)
logrus.Info("Loading nodes")
nodes, err := kube.CoreV1().Nodes().List(ctx, listOpts)
if err != nil {
logrus.Errorf("Error fetching Nodes: %v", err)
return nil, err
}
logrus.Info("Loading namespaces")
var namespaces *corev1.NamespaceList
if c.Namespace != "" {
ns, err := kube.CoreV1().Namespaces().Get(ctx, c.Namespace, metav1.GetOptions{})
@@ -302,14 +301,12 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
}
namespaces = nsList
}
logrus.Info("Loading pods")
pods, err := kube.CoreV1().Pods(c.Namespace).List(ctx, listOpts)
if err != nil {
logrus.Errorf("Error fetching Pods: %v", err)
return nil, err
}
logrus.Info("Setting up restmapper")
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
logrus.Errorf("Error getting API Group resources: %v", err)
@@ -349,8 +346,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
return nil, err
}
logrus.Info("Loading " + kind)
objects, err := dynamic.Resource(mapping.Resource).Namespace(c.Namespace).List(ctx, metav1.ListOptions{})
objects, err := (*dynamic).Resource(mapping.Resource).Namespace(c.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
return nil, err
@@ -366,7 +362,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
objectCache := map[string]unstructured.Unstructured{}
logrus.Info("Loading controllers")
controllers, err := LoadControllers(ctx, pods.Items, dynamic, &restMapper, objectCache)
if err != nil {
logrus.Errorf("Error loading controllers from pods: %v", err)
@@ -378,25 +373,23 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
provider.Nodes = nodes.Items
provider.Namespaces = namespaces.Items
provider.Resources.addResources(kubernetesResources)
logrus.Info("Done loading Kubernetes resources")
return &provider, nil
}
// LoadControllers loads a list of controllers from the kubeResources Pods
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClient dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
interfaces := []GenericResource{}
deduped := map[string]*corev1.Pod{}
for idx, pod := range pods {
deduped := map[string]corev1.Pod{}
for _, pod := range pods {
owners := pod.ObjectMeta.OwnerReferences
if len(owners) == 0 {
deduped[pod.ObjectMeta.Namespace+"/Pod/"+pod.ObjectMeta.Name] = &pods[idx]
deduped[pod.ObjectMeta.Namespace+"/Pod/"+pod.ObjectMeta.Name] = pod
continue
}
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = &pods[idx]
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod
}
for key, pod := range deduped {
logrus.Debugf("Resolving controller from pod %s", key)
workload, err := ResolveControllerFromPod(ctx, *pod, dynamicClient, restMapperPointer, objectCache)
for _, pod := range deduped {
workload, err := ResolveControllerFromPod(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
if err != nil {
return nil, err
}
@@ -426,7 +419,7 @@ func deduplicateControllers(inputResources []GenericResource) []GenericResource
}
func (resources *ResourceProvider) addResourcesFromReader(reader io.Reader) error {
contents, err := io.ReadAll(reader)
contents, err := ioutil.ReadAll(reader)
if err != nil {
logrus.Errorf("Error reading from %v: %v", reader, err)
return err
@@ -478,7 +471,6 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
if err != nil {
return err
}
workload.OriginalObjectYAML = contentBytes
resources.Resources.addResource(workload)
} else {
newResource, err := NewGenericResourceFromBytes(contentBytes)
+3 -3
View File
@@ -17,7 +17,7 @@ package kube
import (
"bytes"
"context"
"os"
"io/ioutil"
"testing"
"time"
@@ -78,7 +78,7 @@ func TestGetMultipleResourceFromBadFile(t *testing.T) {
}
func TestAddResourcesFromReader(t *testing.T) {
contents, err := os.ReadFile("./test_files/test_2/multi.yaml")
contents, err := ioutil.ReadFile("./test_files/test_2/multi.yaml")
assert.NoError(t, err)
reader := bytes.NewBuffer(contents)
resources := newResourceProvider("unknown", "Path", "-")
@@ -146,7 +146,7 @@ func TestGetResourceFromAPI(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, tt.clusterName, dynamicInterface, tt.config)
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, tt.clusterName, &dynamicInterface, tt.config)
if tt.wantErr {
assert.Error(t, err)
+73 -390
View File
@@ -1,435 +1,118 @@
package mutation
import (
"bytes"
"bufio"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v3"
jsonpatchV5 "github.com/evanphx/json-patch/v5"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/pkg/errors"
"github.com/thoas/go-funk"
"gomodules.xyz/jsonpatch/v2"
)
const (
strTag = "!!str"
seqTag = "!!seq"
mapTag = "!!map"
intTag = "!!int"
boolTag = "!!bool"
)
// ApplyAllMutations applies available mutation to a single resource
func ApplyAllMutations(manifest string, mutations []config.Mutation) (string, error) {
var mutated string
var doc yaml.Node
err := yaml.Unmarshal([]byte(manifest), &doc)
// ApplyAllSchemaMutations applies available mutation to a single resource
func ApplyAllSchemaMutations(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource, mutations []jsonpatch.Operation) (kube.GenericResource, error) {
resByte := resource.OriginalObjectJSON
var jsonByte []byte
mutationByte, err := json.Marshal(mutations)
if err != nil {
return mutated, err
return resource, err
}
for _, patch := range mutations {
splits := getSplitFromPath(patch.Path)
valueNode, err := getNodeFromValue(patch.Value, patch.Comment)
if err != nil {
return mutated, err
}
switch patch.Op {
case "add", "replace":
err = addOrReplaceValue(&doc, splits, valueNode)
if err != nil {
return mutated, err
}
case "remove":
// ignore error if the value specified does not exists
_ = removeNodes(&doc, splits)
}
}
buf := bytes.Buffer{}
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)
err = enc.Encode(&doc)
patch, err := jsonpatchV5.DecodePatch(mutationByte)
if err != nil {
return mutated, err
return resource, err
}
err = enc.Close()
jsonByte, err = patch.ApplyWithOptions(resByte, &jsonpatchV5.ApplyOptions{
AllowMissingPathOnRemove: true,
EnsurePathExistsOnAdd: true,
})
if err != nil {
return mutated, err
return resource, err
}
mutated, err := kube.NewGenericResourceFromBytes(jsonByte)
if err != nil {
return resource, err
}
return buf.String(), nil
return mutated, nil
}
// GetMutationsFromResults returns all mutations from results
func GetMutationsFromResults(results []validator.Result) map[string][]config.Mutation {
allMutationsFromResults := make(map[string][]config.Mutation)
// GetMutationsAndCommentsFromResults returns all mutations from results
func GetMutationsAndCommentsFromResults(results []validator.Result) ([]config.MutationComment, map[string][]jsonpatch.Operation) {
allMutationsFromResults := make(map[string][]jsonpatch.Operation)
comments := []config.MutationComment{}
for _, result := range results {
key := fmt.Sprintf("%s/%s/%s", result.Kind, result.Name, result.Namespace)
mutations := GetMutationsFromResult(&result)
mutations, resultsComments := GetMutationsAndCommentsFromResult(&result)
allMutationsFromResults[key] = mutations
comments = append(comments, resultsComments...)
}
return allMutationsFromResults
return comments, allMutationsFromResults
}
// GetMutationsFromResult returns all mutations from single result
func GetMutationsFromResult(result *validator.Result) []config.Mutation {
mutations := []config.Mutation{}
// GetMutationsAndCommentsFromResult returns all mutations from single result
func GetMutationsAndCommentsFromResult(result *validator.Result) ([]jsonpatch.Operation, []config.MutationComment) {
mutations := []jsonpatch.Operation{}
comments := []config.MutationComment{}
for _, resultMessage := range result.Results {
if len(resultMessage.Mutations) > 0 {
mutations = append(mutations, resultMessage.Mutations...)
}
if len(resultMessage.Comments) > 0 {
comments = append(comments, resultMessage.Comments...)
}
}
if result.PodResult != nil {
for _, resultMessage := range result.PodResult.Results {
for _, resultMessage := range result.PodResult.Results {
if len(resultMessage.Mutations) > 0 {
mutations = append(mutations, resultMessage.Mutations...)
}
if len(resultMessage.Comments) > 0 {
comments = append(comments, resultMessage.Comments...)
}
}
for _, containerResult := range result.PodResult.ContainerResults {
for _, resultMessage := range containerResult.Results {
if len(resultMessage.Mutations) > 0 {
mutations = append(mutations, resultMessage.Mutations...)
}
}
for _, containerResult := range result.PodResult.ContainerResults {
for _, resultMessage := range containerResult.Results {
if len(resultMessage.Mutations) > 0 {
mutations = append(mutations, resultMessage.Mutations...)
}
if len(resultMessage.Comments) > 0 {
comments = append(comments, resultMessage.Comments...)
}
}
}
return mutations
return mutations, comments
}
func createPathAndFindNodes(node *yaml.Node, selectors []string, create bool) ([]*yaml.Node, error) {
var nodes []*yaml.Node
currentSelector := selectors[0]
isLastSelector := len(selectors) == 1
// array[N] or array[*] selectors.
if i := strings.LastIndex(currentSelector, "["); i > 0 && strings.HasSuffix(currentSelector, "]") {
arrayIndex := currentSelector[i+1 : len(currentSelector)-1]
currentSelector = currentSelector[:i]
if checkIfNodeExistedInContent(node.Content, currentSelector) || !create {
return findArrayNodes(selectors, currentSelector, node, nodes, arrayIndex, create)
}
index, err := strconv.Atoi(arrayIndex)
if err != nil {
if arrayIndex != "-" {
return nil, errors.Wrapf(err, "can't parse array index from %v/%v/", currentSelector, arrayIndex)
}
// if index provided is greater than or less than 0 for an empty array should throw an exception
} else if index != 0 {
return nil, errors.Errorf("array index (%s) does not exists because array (%s) does not exists", arrayIndex, currentSelector)
}
// default to zero since no node is present.
selectorsToCreateNodes := []string{currentSelector, "0"}
if len(selectors) > 1 {
selectorsToCreateNodes = append(selectorsToCreateNodes, selectors[1:]...)
return createNonExistingPath(selectorsToCreateNodes, node), nil
}
// UpdateMutatedContentWithComments Updates mutated object with comments
func UpdateMutatedContentWithComments(yamlContent string, comments []config.MutationComment) string {
var lines []string
scanner := bufio.NewScanner(strings.NewReader(yamlContent))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
lines = append(lines, line)
}
if currentSelector == "-" {
if isLastSelector {
return []*yaml.Node{node}, nil
}
_, err := createPathAndFindNodes(node, selectors[1:], create)
if err != nil {
return nil, err
commentMap := funk.Map(comments, func(c config.MutationComment) (string, string) {
return c.Find, c.Comment
}).(map[string]string)
fileContent := ""
for _, line := range lines {
comment, ok := commentMap[strings.TrimSpace(line)]
if ok {
line += (" #" + comment)
}
fileContent += line
fileContent += "\n"
}
switch node.Kind {
case yaml.MappingNode:
for i := 0; i < len(node.Content); i += 2 {
// Does the current key match the selector?
if node.Content[i].Value == currentSelector {
// Found last key, return its value.
if !isLastSelector {
// Match the rest of the selector path, ie. go deeper
// in to the value node.
return createPathAndFindNodes(node.Content[i+1], selectors[1:], create)
}
return []*yaml.Node{node.Content[i+1]}, nil
}
}
case yaml.ScalarNode:
// Overwrite any existing nodes.
node.Kind = yaml.MappingNode
node.Tag = mapTag
node.Value = ""
case yaml.SequenceNode:
return nil, errors.Errorf("parent node is array, use /*/ or /0/../%v/ instead of .%v to access its item(s) first", len(node.Content)-1, currentSelector)
default:
return nil, errors.Errorf("parent node is of unknown kind %v", node.Kind)
}
if !create {
return nil, errors.Errorf("failed to match %s", strings.Join(selectors, "/"))
}
return createNonExistingPath(selectors, node), nil
}
func addOrReplaceValue(node *yaml.Node, splits []string, value *yaml.Node) error {
if len(node.Content) == 0 {
return errors.New("No content in node")
}
nodes, err := createPathAndFindNodes(node.Content[0], splits, true)
if err != nil {
return err
}
for _, node := range nodes {
if node.Kind == yaml.ScalarNode {
// Overwrite an existing scalar value with a new value (whatever kind).
*node = *value
} else if node.Kind == yaml.MappingNode && value.Kind == yaml.MappingNode {
// Append new values onto an existing map node.
if len(value.Content) < 2 {
logrus.Error("Found a mapping node without enough content")
continue
}
mapKey := value.Content[0].Value
found := false
for idx, subNode := range node.Content {
if idx%2 != 0 {
continue
}
if subNode.Value == mapKey {
found = true
node.Content[idx+1] = value.Content[1]
}
}
if !found {
node.Content = append(value.Content, node.Content...)
}
} else if node.Kind == yaml.MappingNode && node.Content == nil {
// Overwrite a new map node we created in createPathAndFindNodes(), as confirmed
// by the nil check (the node.Content wouldn't be nil otherwise).
*node = *value
} else if node.Kind == yaml.SequenceNode && value.Kind == yaml.SequenceNode {
// Append new values onto an existing array node.
node.Content = append(node.Content, value.Content...)
} else if node.Kind == yaml.SequenceNode && value.Kind == yaml.ScalarNode {
// Append new value onto an existing array node.
node.Content = append(node.Content, value)
} else {
return errors.Errorf("can't overwrite %v value (line: %v, column: %v) with %v value", node.Tag, node.Line, node.Column, value.Tag)
}
}
return nil
}
func getNodeFromValue(value interface{}, comment string) (*yaml.Node, error) {
bytes, err := yaml.Marshal(value)
if err != nil {
return nil, err
}
var doc yaml.Node
err = yaml.Unmarshal(bytes, &doc)
if err != nil {
return nil, err
}
if len(doc.Content) == 0 {
return nil, errors.New("Generated an empty YAML document")
}
if doc.Content[0].Kind == yaml.MappingNode {
doc.Content[0].Content[0].HeadComment = comment
} else {
doc.Content[0].LineComment = comment
}
return doc.Content[0], nil
}
func removeNodes(doc *yaml.Node, selectors []string) error {
err := removeMatchingNode(doc.Content[0], selectors)
if err != nil {
return errors.Wrapf(err, "failed to match %q", strings.Join(selectors, "/"))
}
return nil
}
func removeMatchingNode(node *yaml.Node, selectors []string) error {
currentSelector := selectors[0]
lastSelector := len(selectors) == 1
if i := strings.LastIndex(currentSelector, "["); i > 0 && strings.HasSuffix(currentSelector, "]") {
arrayIndex := currentSelector[i+1 : len(currentSelector)-1]
currentSelector = currentSelector[:i]
index, err := strconv.Atoi(arrayIndex)
if err != nil {
if arrayIndex == "*" {
index = -1
} else {
return errors.Wrapf(err, "can't parse array index from %v/%v/", currentSelector, arrayIndex)
}
} else if index < 0 {
return errors.Wrapf(err, "array index can't be negative %v/%v/", currentSelector, arrayIndex)
}
// Go into array node(s).
arrayNodes, err := createPathAndFindNodes(node, []string{currentSelector}, false)
if err != nil {
return errors.Errorf("can't find %v", currentSelector)
}
for _, arrayNode := range arrayNodes {
if arrayNode.Kind != yaml.SequenceNode {
return errors.Errorf("%v is not an array", currentSelector)
}
if index >= len(arrayNode.Content) {
return errors.Errorf("%v array doesn't have index %v", currentSelector, index)
}
var visitArrayNodes []*yaml.Node
if index >= 0 { // array[N]
visitArrayNodes = []*yaml.Node{arrayNode.Content[index]}
} else { // array[*]
visitArrayNodes = arrayNode.Content
}
for _, node := range visitArrayNodes {
lastSelector := len(selectors) == 1
if !lastSelector {
removeMatchingNode(node, selectors[1:])
}
}
}
}
// Iterate over the keys (the slice is key/value pairs).
for i := 0; i < len(node.Content); i += 2 {
if node.Content[i].Value == currentSelector {
// Key matches the selector.
if !lastSelector {
// Try to match the rest of the selector path in the value.
return removeMatchingNode(node.Content[i+1], selectors[1:])
}
node.Content[i] = nil // Delete key.
node.Content[i+1] = nil // Delete value.
node.Content = append(node.Content[:i], node.Content[i+2:]...)
return nil
}
}
return errors.Errorf("can't find %q", strings.Join(selectors, "."))
}
func getSplitFromPath(path string) []string {
var digitStarCheck = regexp.MustCompile(`^[0-9*]+$`)
splits := strings.Split(path, "/")
var formatedSplit []string
for _, key := range splits {
if key == "" {
continue
}
if digitStarCheck.MatchString(key) {
lastElementIdx := len(formatedSplit) - 1
lastElement := formatedSplit[lastElementIdx]
lastElement = fmt.Sprintf("%s[%s]", lastElement, key)
formatedSplit[lastElementIdx] = lastElement
continue
}
formatedSplit = append(formatedSplit, key)
}
return formatedSplit
}
func findArrayNodes(selectors []string, currentSelector string, node *yaml.Node, nodes []*yaml.Node, arrayIndex string, create bool) ([]*yaml.Node, error) {
index, err := strconv.Atoi(arrayIndex)
if err != nil {
if arrayIndex == "*" {
index = -1
} else {
return nil, errors.Wrapf(err, "can't parse array index from %v/%v/", currentSelector, arrayIndex)
}
} else if index < 0 {
return nil, errors.Wrapf(err, "array index can't be negative %v/%v/", currentSelector, arrayIndex)
}
// Go into array node(s).
arrayNodes, err := createPathAndFindNodes(node, []string{currentSelector}, create)
if err != nil {
return nil, errors.Errorf("can't find %v", currentSelector)
}
for _, arrayNode := range arrayNodes {
if arrayNode.Kind != yaml.SequenceNode {
return nil, errors.Errorf("%v is not an array", currentSelector)
}
if index >= len(arrayNode.Content) {
return nil, errors.Errorf("%v array doesn't have index %v", currentSelector, index)
}
var visitArrayNodes []*yaml.Node
if index >= 0 { // array[N]
visitArrayNodes = []*yaml.Node{arrayNode.Content[index]}
} else { // array[*]
visitArrayNodes = arrayNode.Content
}
for i, node := range visitArrayNodes {
if len(selectors) == 1 {
// Last selector, use this as final node.
nodes = append(nodes, node)
} else {
// Go deeper into a specific array.
deeperNodes, err := createPathAndFindNodes(node, selectors[1:], create)
if err != nil {
return nil, errors.Wrapf(err, "failed to go deeper into %v/%v/", currentSelector, i)
}
nodes = append(nodes, deeperNodes...)
}
}
}
return nodes, nil
}
func checkIfNodeExistedInContent(nodes []*yaml.Node, currentSelector string) bool {
for i := 0; i < len(nodes); i += 2 {
// Does the current key match the selector?
if nodes[i].Value == currentSelector {
return true
}
}
return false
}
func createNonExistingPath(selectors []string, node *yaml.Node) []*yaml.Node {
var digitDashCheck = regexp.MustCompile(`^[0-9-]+$`)
// Create the rest of the selector path.
for idx, selector := range selectors {
if digitDashCheck.MatchString(selector) {
continue
}
kind, tag := yaml.MappingNode, mapTag
// if the next selector is "-" then current selector is sequence/slice/array
if idx < len(selectors)-1 && digitDashCheck.MatchString(selectors[idx+1]) {
kind, tag = yaml.SequenceNode, seqTag
}
var newNode = yaml.Node{
Content: []*yaml.Node{
{
Kind: yaml.ScalarNode,
Tag: strTag,
Value: selector,
},
{
Kind: kind,
Tag: tag,
},
},
}
// if previous node is array/sequenceNode append a node rather than appending contents
if node.Kind == yaml.SequenceNode {
newNode.Kind = kind
node.Content = append(node.Content, &newNode)
} else {
node.Content = append(node.Content, newNode.Content...)
}
node = newNode.Content[len(newNode.Content)-1]
}
return []*yaml.Node{node}
return fileContent
}
-157
View File
@@ -1,157 +0,0 @@
package mutation
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/fairwindsops/polaris/pkg/config"
)
var oldYaml = `
pets:
- name: fido
owners:
- name: Alice
- name: Bob
aliases:
- Robert
- name: scooby
`
var testCases = []struct {
original string
mutated string
patch config.Mutation
message string
}{{
original: oldYaml,
patch: config.Mutation{
Op: "add",
Value: "Denver",
Path: "/pets/0/owners/*/location",
},
mutated: `pets:
- name: fido
owners:
- name: Alice
location: Denver
- name: Bob
aliases:
- Robert
location: Denver
- name: scooby
`,
}, {
original: oldYaml,
patch: config.Mutation{
Op: "remove",
Path: "/pets/0/owners/*/aliases",
},
mutated: `pets:
- name: fido
owners:
- name: Alice
- name: Bob
- name: scooby
`,
}, {
original: oldYaml,
patch: config.Mutation{
Op: "add",
Value: "rob",
Path: "/pets/0/owners/*/aliases/-",
},
mutated: `pets:
- name: fido
owners:
- name: Alice
aliases:
- rob
- name: Bob
aliases:
- Robert
- rob
- name: scooby
`,
}, {
original: `
pets:
- name: fido
`,
patch: config.Mutation{
Op: "add",
Value: "Alice",
Path: "/pets/0/owners/0/name",
},
mutated: `pets:
- name: fido
owners:
- name: Alice
`,
}, {
original: `
obj:
foo:
bar:
- a
- b
baz: quux
`,
patch: config.Mutation{
Op: "replace",
Value: map[string]interface{}{
"bar": []string{"c", "d"},
},
Path: "/obj/foo",
},
mutated: `obj:
foo:
bar:
- c
- d
baz: quux
`,
}, {
original: `
foo: bar
`,
patch: config.Mutation{
Op: "replace",
Value: "baz",
Path: "/foo",
Comment: "# We set this to baz",
},
mutated: `
foo: baz # We set this to baz
`,
message: "Expected a comment to appear",
}, {
original: `
foo: bar
`,
patch: config.Mutation{
Op: "add",
Value: map[string]interface{}{
"baz": "quux",
},
Path: "/extra",
Comment: "# These are extra things",
},
mutated: `
foo: bar
extra:
# These are extra things
baz: quux
`,
message: "Expected a comment to appear next to an object",
}}
func TestApplyAllMutations(t *testing.T) {
for _, tc := range testCases {
mutated, err := ApplyAllMutations(tc.original, []config.Mutation{tc.patch})
assert.NoError(t, err)
assert.EqualValues(t, strings.TrimSpace(tc.mutated), strings.TrimSpace(mutated), tc.message)
}
}
+1 -1
View File
@@ -109,7 +109,7 @@ func TestControllerLevelChecks(t *testing.T) {
two := int32(2)
d2.Spec.Replicas = &two
k8s, dynamicClient := test.SetupTestAPI(&d1, &p1, &d2, &p2)
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicClient, conf.Configuration{})
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, conf.Configuration{})
assert.Equal(t, err, nil, "error should be nil")
assert.Equal(t, 2, res.Resources.GetLength(), "Should have two controllers")
testResources(res)
+2 -1
View File
@@ -18,6 +18,7 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"time"
@@ -61,7 +62,7 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
// ReadAuditFromFile reads the data from a past audit stored in a JSON or YAML file.
func ReadAuditFromFile(fileName string) AuditData {
auditData := AuditData{}
oldFileBytes, err := os.ReadFile(fileName)
oldFileBytes, err := ioutil.ReadFile(fileName)
if err != nil {
logrus.Errorf("Unable to read contents of loaded file: %v", err)
os.Exit(1)
+1 -1
View File
@@ -33,7 +33,7 @@ func TestGetTemplateData(t *testing.T) {
}
k8s, dynamicClient := test.SetupTestAPI(test.GetMockControllers("test")...)
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicClient, c)
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, c)
assert.Equal(t, err, nil, "error should be nil")
assert.Equal(t, 5, len(resources.Resources))
+5 -3
View File
@@ -21,6 +21,7 @@ import (
"github.com/fatih/color"
"github.com/thoas/go-funk"
"gomodules.xyz/jsonpatch/v2"
"github.com/fairwindsops/polaris/pkg/config"
)
@@ -88,7 +89,8 @@ type ResultMessage struct {
Success bool
Severity config.Severity
Category string
Mutations []config.Mutation
Mutations []jsonpatch.Operation
Comments []config.MutationComment
}
// ResultSet contiains the results for a set of checks
@@ -186,7 +188,7 @@ func fillString(id string, l int) string {
// GetPrettyOutput returns a human-readable string
func (res AuditData) GetPrettyOutput(useColor bool) string {
color.NoColor = !useColor
str := titleColor.Sprint(fmt.Sprintf("Polaris audited %s %s at %s\n", res.SourceType, res.SourceName, res.AuditTime))
str := titleColor.Sprint(fmt.Sprintf("\n\nPolaris audited %s %s at %s\n", res.SourceType, res.SourceName, res.AuditTime))
str += color.CyanString(fmt.Sprintf(" Nodes: %d | Namespaces: %d | Controllers: %d\n", res.ClusterInfo.Nodes, res.ClusterInfo.Namespaces, res.ClusterInfo.Controllers))
str += color.GreenString(fmt.Sprintf(" Final score: %d\n", res.Score))
str += "\n"
@@ -215,7 +217,7 @@ func (res Result) GetPrettyOutput() string {
func (res PodResult) GetPrettyOutput() string {
str := res.Results.GetPrettyOutput()
for _, cont := range res.ContainerResults {
str += cont.GetPrettyOutput()
str += cont.GetPrettyOutput() + "\n"
}
return str
}
+13 -49
View File
@@ -24,6 +24,7 @@ import (
"github.com/qri-io/jsonschema"
"github.com/sirupsen/logrus"
"github.com/thoas/go-funk"
"gomodules.xyz/jsonpatch/v2"
corev1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -40,25 +41,6 @@ type schemaTestCase struct {
ResourceProvider *kube.ResourceProvider
}
// ShortString supplies some fields of a schemaTestCase suitable for brief
// output.
func (s schemaTestCase) ShortString() string {
var msg strings.Builder
targetStr := s.Target
if targetStr != "" {
msg.WriteString(fmt.Sprintf("target=%s, ", targetStr))
}
ns := s.Resource.ObjectMeta.GetNamespace()
if ns != "" {
msg.WriteString(fmt.Sprintf("namespace=%s, ", ns))
}
msg.WriteString(fmt.Sprintf("resource=%s/%s", s.Resource.Kind, s.Resource.ObjectMeta.GetName()))
if s.Target == config.TargetContainer {
msg.WriteString(fmt.Sprintf(", container=%s", s.Container.Name))
}
return msg.String()
}
func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) {
if !conf.DisallowExemptions &&
!conf.DisallowAnnotationExemptions &&
@@ -129,7 +111,6 @@ func getTemplateInput(test schemaTestCase) (map[string]interface{}, error) {
}
}
}
logrus.Debugf("the go template input for schema test-case %s is: %v", test.ShortString(), templateInput)
return templateInput, nil
}
@@ -174,9 +155,6 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
// ApplyAllSchemaChecksToResourceProvider applies all available checks to a ResourceProvider
func ApplyAllSchemaChecksToResourceProvider(conf *config.Configuration, resourceProvider *kube.ResourceProvider) ([]Result, error) {
results := []Result{}
if resourceProvider == nil {
return nil, errors.New("No resource provider set, cannot apply schema checks")
}
for _, resources := range resourceProvider.Resources {
kindResults, err := ApplyAllSchemaChecksToAllResources(conf, resourceProvider, resources)
if err != nil {
@@ -195,9 +173,7 @@ func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resourceProv
if err != nil {
return results, err
}
if result.Kind != "" && result.Name != "" {
results = append(results, result)
}
results = append(results, result)
}
return results, nil
}
@@ -377,17 +353,13 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
if !passes {
break
}
if test.ResourceProvider == nil {
logrus.Warnf("No ResourceProvider available, check %s will not work in this context (e.g. admission control)", checkID)
break
}
resources := test.ResourceProvider.Resources[groupkind]
namespace := test.Resource.ObjectMeta.GetNamespace()
if test.Resource.Kind == "Namespace" {
namespace = test.Resource.ObjectMeta.GetName()
}
resources = funk.Filter(resources, func(res kube.GenericResource) bool {
return res.ObjectMeta.GetNamespace() == "" || res.ObjectMeta.GetNamespace() == namespace
return res.ObjectMeta.GetNamespace() == namespace
}).([]kube.GenericResource)
objects := funk.Map(resources, func(res kube.GenericResource) interface{} {
return res.Resource.Object
@@ -397,25 +369,16 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
return nil, err
}
}
if len(issues) > 0 {
issueMessages := make([]string, len(issues))
for i, issue := range issues {
issueMessages[i] = issue.Message
}
logrus.Debugf("there were %d issue(s) validating the schema for test-case %s: %v", len(issueMessages), test.ShortString(), issueMessages)
} else {
logrus.Debugf("there were no issues validating the schema for test-case %s", test.ShortString())
}
result := makeResult(conf, check, passes, issues)
if !passes {
if funk.Contains(conf.Mutations, checkID) && len(check.Mutations) > 0 {
mutations := funk.Map(check.Mutations, func(mutation config.Mutation) config.Mutation {
mutations := funk.Map(check.Mutations, func(mutation jsonpatch.Operation) jsonpatch.Operation {
mutationCopy := deepCopyMutation(mutation)
mutationCopy.Path = prefix + mutationCopy.Path
return mutationCopy
}).([]config.Mutation)
}).([]jsonpatch.Operation)
result.Mutations = mutations
result.Comments = check.Comments
}
}
return &result, nil
@@ -430,12 +393,11 @@ func getSortedKeys(m map[string]config.Severity) []string {
return keys
}
func deepCopyMutation(source config.Mutation) config.Mutation {
destination := config.Mutation{
Op: source.Op,
Path: source.Path,
Value: source.Value,
Comment: source.Comment,
func deepCopyMutation(source jsonpatch.Operation) jsonpatch.Operation {
destination := jsonpatch.Operation{
Operation: source.Operation,
Path: source.Path,
Value: source.Value,
}
return destination
}
@@ -448,6 +410,8 @@ func getJSONSchemaPrefix(kind string) (prefix string) {
} else if (kind == "Deployment") || (kind == "DaemonSet") ||
(kind == "StatefulSet") || (kind == "Job") || (kind == "ReplicationController") {
prefix = "/spec/template/spec"
} else {
logrus.Warningf("Mutation for this this resource (%s) is not supported", kind)
}
return prefix
}
+3 -12
View File
@@ -25,7 +25,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"sigs.k8s.io/yaml"
)
// Mutator mutate k8s resources.
@@ -45,20 +44,12 @@ func NewMutateWebhook(mgr manager.Manager, mutator Mutator) {
}
func (m *Mutator) mutate(req admission.Request) ([]jsonpatch.Operation, error) {
results, kubeResources, err := GetValidatedResults(req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
results, err := GetValidatedResults(req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
if err != nil {
return nil, err
}
patches := mutation.GetMutationsFromResult(results)
originalYaml, err := yaml.JSONToYAML(kubeResources.OriginalObjectJSON)
if err != nil {
return nil, err
}
mutatedYamlStr, err := mutation.ApplyAllMutations(string(originalYaml), patches)
if err != nil {
return nil, err
}
return jsonpatch.CreatePatch(originalYaml, []byte(mutatedYamlStr))
patches, _ := mutation.GetMutationsAndCommentsFromResult(results)
return patches, nil
}
// Handle for Validator to run validation checks.
+9 -8
View File
@@ -54,42 +54,43 @@ func NewValidateWebhook(mgr manager.Manager, validator Validator) {
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
}
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, kube.GenericResource, error) {
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, error) {
return GetValidatedResults(req.AdmissionRequest.Kind.Kind, v.decoder, req, v.Config)
}
// GetValidatedResults returns the validated results.
func GetValidatedResults(kind string, decoder *admission.Decoder, req admission.Request, config config.Configuration) (*validator.Result, kube.GenericResource, error) {
func GetValidatedResults(kind string, decoder *admission.Decoder, req admission.Request, config config.Configuration) (*validator.Result, error) {
var controller kube.GenericResource
var err error
if kind == "Pod" {
pod := corev1.Pod{}
err := decoder.Decode(req, &pod)
if err != nil {
return nil, controller, err
return nil, err
}
if len(pod.ObjectMeta.OwnerReferences) > 0 {
logrus.Infof("Allowing owned pod %s/%s to pass through webhook", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
return nil, controller, nil
return nil, nil
}
controller, err = kube.NewGenericResourceFromPod(pod, pod)
} else {
controller, err = kube.NewGenericResourceFromBytes(req.Object.Raw)
}
if err != nil {
return nil, controller, err
return nil, err
}
// TODO: consider enabling multi-resource checks
controllerResult, err := validator.ApplyAllSchemaChecks(&config, nil, controller)
if err != nil {
return nil, controller, err
return nil, err
}
return &controllerResult, controller, nil
return &controllerResult, nil
}
// Handle for Validator to run validation checks.
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
logrus.Info("Starting request")
result, _, err := v.handleInternal(req)
result, err := v.handleInternal(req)
if err != nil {
logrus.Errorf("Error validating request: %v", err)
return admission.Errored(http.StatusBadRequest, err)
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env sh
# Wrap goreleaser by using envsubst on .goreleaser.yml,
# and creating a temporary git tag.
function cleanup {
if [ "${CIRCLE_TAG}" == "" ] ; then
echo "${this_script} deleting git tag ${temporary_git_tag} for goreleaser"
unset GORELEASER_CURRENT_TAG
git tag -d ${temporary_git_tag}
fi
}
set -eE # errexit and errtrace
trap 'cleanup' ERR
this_script="$(basename $0)"
hash envsubst
hash goreleaser
if [ "${TMPDIR}" == "" ] ; then
export TMPDIR="/tmp"
echo "${this_script} temporarily set the TMPDIR environment variable to ${TMPDIR}, used for a temporary GOBIN environment variable"
fi
export skip_feature_docker_tags=false
export skip_release=true
if [ "${CIRCLE_TAG}" == "" ] ; then
# Create a temporary tag for goreleaser, incrementing the last tag.
last_git_tag="$(git describe --tags --abbrev=0 2>/dev/null)"
if [ "${last_git_tag}" == "" ] ; then
echo "${this_script} is unable to determine the last git tag so a temporary tag can be created, using: git describe --tags --abbrev=0"
exit 1
fi
if [ "$(git config user.email)" == "" ] ; then
# git will use this env var as its user.email.
# git tag -m is used in case tags are manually pushed by accident,
# however git tag -m requires an email.
export EMAIL='goreleaser_ci@fairwinds.com'
echo "${this_script} using ${EMAIL} temporarily as the git user.email"
fi
temporary_git_tag=$(echo "${last_git_tag}" | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g')-rc
echo "${this_script} creating temporary git tag ${temporary_git_tag} for goreleaser, the last real tag is ${last_git_tag}"
# The -f is included to overwrite existing tags, perhaps from previous CI jobs.
git tag -f -m "temporary local tag for goreleaser" ${temporary_git_tag}
export GORELEASER_CURRENT_TAG=${temporary_git_tag}
# Use an adjusted git feature branch name as a docker tag.
export feature_docker_tag=$(echo "${CIRCLE_BRANCH:0:26}" | sed 's/[^a-zA-Z0-9]/-/g' | sed 's/-\+$//')
echo "${this_script} also using docker tag ${feature_docker_tag} since ${CIRCLE_BRANCH} is a feature branch"
else
export GORELEASER_CURRENT_TAG=${CIRCLE_TAG}
echo "${this_script} setting skip_release to false, and skip_feature_docker_tags to true, because CIRCLE_TAG is set"
export skip_feature_docker_tags=true
export skip_release=false
fi
echo "${this_script} using git tag ${GORELEASER_CURRENT_TAG}"
# Only substitute specific variables, as goreleaser uses shell variable syntax
# for its `signs` section `signature` and `artifact` variables.
cat .goreleaser.yml.envsubst |envsubst '${skip_release} ${skip_feature_docker_tags} ${feature_docker_tag}' >.goreleaser.yml
goreleaser $@
if [ $? -eq 0 ] ; then
echo "${this_script} removing the temporary .goreleaser.yml since goreleaser was successful"
rm .goreleaser.yml # Keep git clean for additional goreleaser runs
fi
cleanup
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/env sh
set -e
# Install packr2 and generate packr boxes for Polaris.
# IDeally this script is called with $GOBIN already set to a temporary
# directory, where packr2 will be installed.
if [ "x${GOBIN}" != "x" ] ; then
PATH=$GOBIN:$PATH
fi
go install github.com/gobuffalo/packr/v2/packr2@latest
packr2
@@ -1,9 +0,0 @@
# This fails because the ClusterRole apiGroups, resources, and verbs are all * which allows pods/exec|attach.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/attach.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/attach" ]
verbs: [ create ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/attach using all verbs.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/attach" ]
verbs: [ "*" ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/attach|exec using all resources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "*" ]
verbs: [ create ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/attach using all apiGroups.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "pods/attach" ]
verbs: [ create ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/exec using all apiGroups.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "pods/exec" ]
verbs: [ create ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/exec.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/exec" ]
verbs: [ create ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/exec using all verbs.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/exec" ]
verbs: [ "*" ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/attach|exec using all resources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "*" ]
verbs: [ get ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/attach using all apiGroups.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "pods/attach" ]
verbs: [ get ]
@@ -1,9 +0,0 @@
# This fails because the ClusterRole allows pods/exec using all apiGroups.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "pods/exec" ]
verbs: [ get ]
@@ -1,9 +0,0 @@
# This succeeds because the ClusterRole does not allow pods/exec|attach.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ get, create ]
@@ -1,9 +0,0 @@
# This succeeds because the ClusterRole allows pods/attach but with a safe verb.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/attach" ]
verbs: [ list ]
@@ -1,9 +0,0 @@
# This succeeds because the ClusterRole is an expected default one (by name).
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: admin
rules:
- apiGroups: [ "" ]
resources: [ "pods/attach" ]
verbs: [ "create" ]
@@ -1,9 +0,0 @@
# This succeeds because the ClusterRole is an expected default one (by name).
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-admin
rules:
- apiGroups: [ "" ]
resources: [ "pods/exec" ]
verbs: [ "create" ]
@@ -1,9 +0,0 @@
# This succeeds because the ClusterRole allows pods/exec but with a safe verb.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/exec" ]
verbs: [ list ]
@@ -1,9 +0,0 @@
# This succeeds because the clusterRole is an exempt name `gce:podsecuritypolicy:calico-sa`
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gce:podsecuritypolicy:calico-sa
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
@@ -1,9 +0,0 @@
# This succeeds because the clusterRole has an exempt `system:` prefix.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
@@ -1,33 +0,0 @@
# This fails because the clusterRoleBinding references a ClusterRole that uses all wildcards and all verbs.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,33 +0,0 @@
# This fails because the clusterRoleBinding references a ClusterRole that uses all wildcards and all verbs (in different order from failure.all_verbs.yaml).
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "get", "list", "delete", "update", "create", "patch", "watch" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,35 +0,0 @@
# This fails because the clusterRoleBinding references a ClusterRole that uses all wildcards which happens to have a `system:` prefix.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# The system: prefix does not cause this test to fail, but this test
# avoids incorectly ignoring user-created bindings to system ClusterRoles.
name: system:test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test-binding-to-system-prefix-clusterrole
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,26 +0,0 @@
# This fails because the ClusterRoleBinding references the default cluster-admin ClusterRole.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This ClusterRole emulates the default cluster-admin one, only by name.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-admin
rules:
- apiGroups:
- ''
resources:
- 'deployments'
verbs:
- 'get'
@@ -1,33 +0,0 @@
# This fails because the clusterRoleBinding references a ClusterRole that uses all wildcards.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,22 +0,0 @@
# This succeeds because the ClusterRoleBinding references a ClusterRole with safe permissions.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "deployments" ]
verbs: [ "get", "list" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
@@ -1,33 +0,0 @@
# This succeeds because the clusterRoleBinding references a ClusterRole with a safer subset of apiGroups.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "*" ]
verbs: [ "*" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,34 +0,0 @@
# This succeeds because the clusterRoleBinding references a ClusterRole with a safer subset of resources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "pods" ]
verbs: [ "*" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,33 +0,0 @@
# This succeeds because the clusterRoleBinding references a ClusterRole with a safer subset of verbs.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "get", "list" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,33 +0,0 @@
# This succeeds because the clusterRoleBinding is an exempt name `gce:podsecuritypolicy:calico-sa`
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gce:podsecuritypolicy:calico-sa
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,33 +0,0 @@
# This succeeds because the clusterRoleBinding has an exempt `system:` prefix.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: system:test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
---
# This Role exists so there is at least one Role for the additionalSchema to find.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: not-used
namespace: test
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ list ]
@@ -1,22 +0,0 @@
# This fails because the ClusterRole apiGroups, resources, and verbs are all * which allows pods/exec|attach.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "*" ]
resources: [ "*" ]
verbs: [ "*" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
@@ -1,22 +0,0 @@
# This fails because the ClusterRole allows pods/attach.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/attach" ]
verbs: [ create ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: test
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser

Some files were not shown because too many files have changed in this diff Show More