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
209 changed files with 4296 additions and 9898 deletions
+93 -35
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,12 +152,15 @@ 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
@@ -138,61 +174,83 @@ jobs:
name: Insights CI
command: curl -L https://insights.fairwinds.com/v0/insights-ci.sh | bash
build_and_push:
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]+/
tags:
ignore: /^testing-.*/
- insights:
requires:
- build_and_push
- 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
View File
@@ -1,4 +1,3 @@
This PR fixes #
## Checklist
+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
+19 -3
View File
@@ -1,11 +1,27 @@
FROM alpine:3.17
FROM golang:1.17 AS build-env
WORKDIR /go/src/github.com/fairwindsops/polaris/
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
+8 -5
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>
-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 -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*'
+4 -4
View File
@@ -19,7 +19,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
@@ -137,7 +137,7 @@ func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
return "", err
}
dir, err := os.MkdirTemp("", "*")
dir, err := ioutil.TempDir("", "*")
if err != nil {
return "", err
}
@@ -212,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)
@@ -223,7 +223,7 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
}
if outputFile != "" {
err := os.WriteFile(outputFile, []byte(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 -1
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
+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()
@@ -27,26 +27,3 @@ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TM95WXQ');
!function() {
var t = window.driftt = window.drift = window.driftt || [];
if (!t.init) {
if (t.invoked) return void (window.console && console.error && console.error("Drift snippet included twice."));
t.invoked = !0, t.methods = [ "identify", "config", "track", "reset", "debug", "show", "ping", "page", "hide", "off", "on" ],
t.factory = function(e) {
return function() {
var n = Array.prototype.slice.call(arguments);
return n.unshift(e), t.push(n), t;
};
}, t.methods.forEach(function(e) {
t[e] = t.factory(e);
}), t.load = function(t) {
var e = 3e5, n = Math.ceil(new Date() / e) * e, o = document.createElement("script");
o.type = "text/javascript", o.async = !0, o.crossorigin = "anonymous", o.src = "https://js.driftt.com/include/" + n + "/" + t + ".js";
var i = document.getElementsByTagName("script")[0];
i.parentNode.insertBefore(o, i);
};
}
}();
drift.SNIPPET_VERSION = '0.3.1';
drift.load('dp7v3zbc7xhm');
-5
View File
@@ -5,11 +5,6 @@ meta:
content: "Fairwinds Polaris | Changelog"
---
## 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
-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.
+3174 -4811
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"
-8
View File
@@ -27,14 +27,6 @@ checks:
hostPortSet: 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
-105
View File
@@ -16,11 +16,8 @@ checks:
memoryRequestsMissing: warning
memoryLimitsMissing: warning
# security
automountServiceAccountToken: ignore
hostIPCSet: danger
hostPIDSet: danger
linuxHardening: warning
missingNetworkPolicy: ignore
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: danger
@@ -30,62 +27,11 @@ checks:
hostNetworkSet: danger
hostPortSet: warning
tlsSettingsMissing: warning
# These are initially warning and will later be promoted to danger.
sensitiveContainerEnvVar: warning
sensitiveConfigmapContent: warning
clusterrolePodExecAttach: warning
rolePodExecAttach: warning
clusterrolebindingPodExecAttach: warning
rolebindingClusterRolePodExecAttach: warning
rolebindingRolePodExecAttach: warning
clusterrolebindingClusterAdmin: warning
rolebindingClusterAdminClusterRole: warning
rolebindingClusterAdminRole: warning
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
@@ -108,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:
@@ -167,9 +72,6 @@ exemptions:
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- controllerNames:
- cluster-autoscaler
@@ -177,9 +79,6 @@ exemptions:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- readinessProbeMissing
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- controllerNames:
- vpa
@@ -196,10 +95,6 @@ exemptions:
- readinessProbeMissing
- livenessProbeMissing
- notReadOnlyRootFilesystem
- automountServiceAccountToken
- linuxHardening
- missingNetworkPolicy
- sensitiveContainerEnvVar
- controllerNames:
- nginx-ingress-controller
+48 -51
View File
@@ -1,64 +1,60 @@
module github.com/fairwindsops/polaris
go 1.19
go 1.17
require (
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.5.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.0
k8s.io/apimachinery v0.26.0
k8s.io/client-go v0.26.0
sigs.k8s.io/controller-runtime v0.14.1
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.14.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
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.28 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.21 // 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.1 // 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.3 // 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
@@ -66,31 +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/crypto v0.4.0 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/oauth2 v0.3.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.3.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.0 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 // indirect
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // 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
)
+398 -155
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -59,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)
}
+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
@@ -1,22 +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: [ "*" ]
---
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,13 +0,0 @@
# This fails because the roleRef points to a ClusterRole that does not exist.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: does-not-exist
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: testuser
@@ -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,22 +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 ]
---
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 using all apiGroups.
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
@@ -1,22 +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 ]
---
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/exec.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "pods/exec" ]
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
@@ -1,22 +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: [ "*" ]
---
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|exec using all resources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [ "" ]
resources: [ "*" ]
verbs: [ get ]
---
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