Compare commits

..
4 Commits
Author SHA1 Message Date
Robert Brennan 9e0ed9ed33 Update docs (#472)
* move docs files

* update docs from template

* another update

* add insights callouts

* add ingress check to the docs

* remove old prefixes on check names
2021-01-15 21:21:51 +00:00
Robert Brennan 744e40a888 remove details links (#473) 2021-01-15 21:20:50 +00:00
Robert Brennan 6ca4e8a43d Update README.md (#464) 2021-01-15 21:20:31 +00:00
Robert Brennan 267509e73d Add architecture diagram (#458)
* Add files via upload

* Update README.md

* Update architecture.svg

* Delete architecture.svg

* Add files via upload

* Delete architecture.svg

* Add files via upload

* fix images
2021-01-15 21:20:13 +00:00
404 changed files with 5474 additions and 30015 deletions
+89 -61
View File
@@ -1,9 +1,5 @@
version: 2.1
orbs:
rok8s: fairwinds/rok8s-scripts@11
oss-docs: fairwinds/oss-docs@0
executors:
vm:
machine:
@@ -21,8 +17,6 @@ references:
echo 'export PUSH_ALL_VERSION_TAGS=true' >> ${BASH_ENV}
echo 'export GOPROXY=https://proxy.golang.org' >> ${BASH_ENV}
echo 'export GO111MODULE=on' >> ${BASH_ENV}
echo 'export GOFLAGS=-mod=mod' >> ${BASH_ENV}
echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
install_k8s: &install_k8s
run:
@@ -33,20 +27,20 @@ references:
sudo apt-get install -yqq jq git
echo "Installing KIND"
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.14.0/kind-linux-amd64
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.8.1/kind-linux-amd64
chmod 0755 kind-linux-amd64
sudo mv kind-linux-amd64 /usr/local/bin/kind
kind version
echo "Installing Kubectl"
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.21.12/bin/linux/amd64/kubectl
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.18.6/bin/linux/amd64/kubectl
chmod 0755 kubectl
sudo mv kubectl /usr/local/bin/
kubectl version --client
echo "Creating Kubernetes Cluster with Kind"
kind create cluster --wait=90s --image kindest/node:v1.21.12
kind create cluster --wait=90s --image kindest/node:v1.15.11
docker ps -a
kubectl version
@@ -64,6 +58,16 @@ references:
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
echo "Install cert-manager successful"
# Test scripts
update_coverage: &update_coverage
run:
name: Update Coverage
command: |
if [[ -z $CIRCLE_PR_NUMBER ]]; then
bash <(curl -s https://codecov.io/bash)
else
echo "Skipping coverage for forked PR"
fi
test_binary_dashboard: &test_binary_dashboard
run:
name: Test Dashboard
@@ -87,6 +91,23 @@ references:
echo "Skipping Kubernetes tests for forked PR"
fi
# Release scripts
install_goreleaser: &install_goreleaser
run:
name: Install GoReleaser
command: |
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.131.1/goreleaser_amd64.deb
echo "640790dcbfa864f26de4c26c2d491f293a64525c8c6641c5bbdec7136b38977e goreleaser.deb" | sha256sum -c -
sudo dpkg -i goreleaser.deb
rm goreleaser.deb
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
@@ -96,27 +117,25 @@ references:
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
release_deploy_configs: &release_deploy_configs
run:
name: enable experimental features
name: Release deploy configs
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
command: |
apk --update add curl yq
cd /tmp
curl -LO https://releases.hashicorp.com/vault/1.9.3/vault_1.9.3_linux_amd64.zip
unzip vault_1.9.3_linux_amd64.zip
mv vault /usr/bin/vault
upload_url=$(curl --silent https://api.github.com/repos/FairwindsOps/polaris/releases/latest | grep upload_url)
upload_url=$(echo $upload_url | sed -e 's/.*\(https.*\){.*$/\1/')
curl -X POST "$upload_url?name=dashboard.yaml" --data-binary "@./deploy/dashboard.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
curl -X POST "$upload_url?name=webhook.yaml" --data-binary "@./deploy/webhook.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
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
@@ -136,47 +155,34 @@ jobs:
- *test_k8s
test:
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: cimg/go:1.19
- image: circleci/golang:1.13
steps:
- checkout
- *set_environment_variables
- run: go vet ./...
- run: go test ./... -coverprofile=coverage.txt -covermode=count
- 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 ./pkg/... -coverprofile=coverage.txt -covermode=count
- run: go run main.go audit --audit-path ./deploy --set-exit-code-below-score 100 --set-exit-code-on-danger
- *update_coverage
- *test_binary_dashboard
insights:
docker:
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
- run:
name: Insights CI
command: curl -L https://insights.fairwinds.com/v0/insights-ci.sh | bash
release_binary:
working_directory: /go/src/github.com/fairwindsops/polaris/
resource_class: large
shell: /bin/bash
docker:
- image: goreleaser/goreleaser:v1.11.4
- image: circleci/golang:1.13
steps:
- checkout
- setup_remote_docker:
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
- setup_remote_docker
- *set_environment_variables
- *install_goreleaser
- 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_deploy_configs
release_images:
working_directory: /go/src/github.com/fairwindsops/polaris/
@@ -187,22 +193,45 @@ jobs:
- setup_remote_docker
- *set_environment_variables
- *docker_build_and_push
publish_docs:
docker:
- image: cimg/node:15.5.1
steps:
- checkout
- run:
name: Build Docs Site
command: |
set -e
cd ./docs
npm install
npm run check-links
npm run build
- run:
name: Install AWS CLI
command: |
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
- run:
name: Publish Docs Site to S3
command: |
cd ./dist
aws s3 sync ./ s3://polaris.docs.fairwinds.com --delete
workflows:
version: 2
test_and_push:
build:
jobs:
- test
- build:
requires:
- test
- push:
context: org-global
requires:
- test
filters:
branches:
ignore: /pull\/[0-9]+/
- insights:
requires:
- push
- build
filters:
branches:
ignore: /pull\/[0-9]+/
@@ -231,8 +260,7 @@ workflows:
ignore: /.*/
tags:
ignore: /^testing-.*/
- oss-docs/publish-docs:
repository: polaris
- publish_docs:
filters:
branches:
ignore: /.*/
-58
View File
@@ -1,58 +0,0 @@
name: Bug Report
description: File a bug report
labels: [bug, triage]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please fill the form below.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: What happened?
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect to happen?
description: What is the expected or desired behavior?
validations:
required: true
- type: textarea
id: reproducible
attributes:
label: How can we reproduce this?
description: Please share the steps that we can take to reproduce this. Also include any relevant configuration.
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: The version of the tool that you are using. If a helm chart, please share the name of the chart.
validations:
required: true
- type: checkboxes
id: search
attributes:
label: Search
options:
- label: I did search for other open and closed issues before opening this.
required: true
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow the CODE_OF_CONDUCT in this repository.
options:
- label: I agree to follow this project's Code of Conduct
required: true
- type: textarea
id: ctx
attributes:
label: Additional context
description: Anything else you would like to add
validations:
required: false
+58
View File
@@ -0,0 +1,58 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
---
name: Bug report
about: Let us know about an unexpected error, a crash, or an incorrect behavior.
---
### Installation Process
<!---
Did you install polaris on your local machine, or in a kubernetes cluster? Did you use kubectl, helm, brew, a GitHub release, or the source code? Let us know below.
-->
### Polaris Version
<!---
If you are using polaris locally, run `polaris --version` to show the version, and paste the result between the ``` marks below.
If you are not running the latest version of Polaris, please try upgrading because your issue may have already been fixed.
-->
```
...
```
### Expected Behavior
<!--
What should have happened?
-->
### Actual Behavior
<!--
What actually happened?
-->
### Steps to Reproduce
<!--
Please list the full steps required to reproduce the issue, for example:
1. `polaris --audit --output-format score`
-->
### Additional Context
<!--
Are there anything atypical about your situation that we should know?
-->
### References
<!--
Are there any other GitHub issues (open or closed) or Pull Requests that should be linked here? For example:
- #6017
-->
+1 -1
View File
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: [triage, enhancement]
labels: ''
assignees: ''
---
+3 -1
View File
@@ -2,7 +2,9 @@
name: Other
about: For misc. tasks like research or continued conversation
title: ''
labels: [triage]
labels: ''
assignees: ''
---
-15
View File
@@ -1,15 +0,0 @@
# The action uses an own Dockerfile on purpose because the root Dockerfile takes way too long to build for an action
FROM alpine:3.10
RUN apk add --no-cache \
bash \
ca-certificates \
curl \
wget \
tar \
jq
COPY get_polaris.sh /get_polaris.sh
ENTRYPOINT ["/get_polaris.sh"]
-22
View File
@@ -1,22 +0,0 @@
name: 'Install polaris'
description: 'Download a specific polaris version'
inputs:
version:
description: 'version of polaris'
required: true
default: 'latest'
runs:
using: 'docker'
image: './Dockerfile'
args:
- ${{ inputs.version }}
outputs:
version:
description: 'Version of polaris installed'
branding:
icon: 'download-cloud'
color: 'gray-dark'
@@ -1,20 +0,0 @@
#!/bin/bash
if [[ -z "$INPUT_VERSION" ]]; then
echo "Missing polaris version information"
exit 1
fi
POLARIS_URL=https://github.com/FairwindsOps/polaris/releases/download/$INPUT_VERSION/polaris_linux_amd64.tar.gz
polaris version | grep "$INPUT_VERSION" &> /dev/null
if [ $? == 0 ]; then
echo "Polaris $INPUT_VERSION is already installed! Exiting gracefully."
exit 0
else
echo "Installing polaris to path from " $POLARIS_URL
fi
TARGET_FILE="polaris.tar.gz"
curl -LJ -o $TARGET_FILE $POLARIS_URL
mkdir polaris
tar -xzf $TARGET_FILE -C polaris
rm $TARGET_FILE
echo "polaris" >> $GITHUB_PATH
echo "::set-output name=version::$INPUT_VERSION"
-20
View File
@@ -1,20 +0,0 @@
## DO NOT EDIT - Managed by Terraform
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/docs"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
-14
View File
@@ -1,14 +0,0 @@
This PR fixes #
## Checklist
* [ ] I have signed the CLA
* [ ] I have updated/added any relevant documentation
## Description
### What's the goal of this PR?
### What changes did you make?
### What alternative solution should we consider, if any?
+47
View File
@@ -0,0 +1,47 @@
# This file is generated from FairwindsOps/documentation-template
# DO NOT EDIT MANUALLY
name: Build Website
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
defaults:
run:
working-directory: ./docs-md
env:
CI: true
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- name: Build site
run: npm run build
- name: Check links
run: npm run check-links
- name: Push changes
run: |
username="GitHub Actions"
git config user.email "opensource@fairwinds.com"
git config user.name $username
HAS_CHANGE=$(git diff .)
if [ -n "${HAS_CHANGE}" ]; then
if [ "$(git log -1 --pretty=format:'%an')" == $username ]; then
echo "Build created a diff, but the last commit was a build."
exit 1
fi
git add ../docs/
git commit -m "[CI] rebuild website"
git push -u origin +master:website
fi
-18
View File
@@ -1,18 +0,0 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '32 1 * * *'
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
exempt-issue-labels: pinned
stale-pr-label: stale
stale-issue-label: stale
-27
View File
@@ -1,27 +0,0 @@
name: Test setup-polaris
on:
pull_request:
branches: [ master ]
jobs:
build-int:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup polaris
uses: ./.github/actions/setup-polaris
with:
version: 4.2.0
- name: Use command
run: polaris version
build-ext:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup polaris
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
version: 4.2.0
- name: Use command
run: polaris version
-1
View File
@@ -1,5 +1,4 @@
# Binaries for programs and plugins
.go-version
*.exe
*.exe~
*.dll
+3 -24
View File
@@ -7,42 +7,21 @@ changelog:
- '^docs:'
- '^test:'
builds:
- id: polaris
ldflags:
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
- id: watcher
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
- 386
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:
github:
owner: FairwindsOps
name: homebrew-tap
folder: Formula
-24
View File
@@ -1,24 +0,0 @@
header:
license:
spdx-id: Apache-2.0
copyright-owner: 'FairwindsOps, Inc.'
content: |
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
paths:
- '**/*.go'
comment: on-failure
dependency:
files:
- go.mod
+1 -2
View File
@@ -1,2 +1 @@
## DO NOT EDIT - Managed by Terraform
* @rbren @makoscafee
* @rbren @makoscafee @jordandoig @baderbuddy
-74
View File
@@ -1,74 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at opensource@fairwinds.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+3 -4
View File
@@ -1,4 +1,4 @@
FROM golang:1.19 AS build-env
FROM golang:1.13 AS build-env
WORKDIR /go/src/github.com/fairwindsops/polaris/
ENV GO111MODULE=on
@@ -10,14 +10,13 @@ ENV GOARCH=amd64
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go install github.com/gobuffalo/packr/v2/packr2@latest
RUN go get -u github.com/gobuffalo/packr/v2/packr2
COPY . .
RUN packr2 build -a -o polaris *.go
FROM alpine:3.16
FROM alpine:3.10
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
+40 -37
View File
@@ -1,63 +1,66 @@
<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>
<a href="https://github.com/FairwindsOps/polaris/releases">
<img src="https://img.shields.io/github/v/release/FairwindsOps/polaris">
<h3>Best Practices for Kubernetes Workload Configuration</h3>
<a href="https://github.com/FairwindsOps/polaris">
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.0.0&color=239922">
</a>
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
</a>
<a href="https://circleci.com/gh/FairwindsOps/polaris">
<a href="https://circleci.com/gh/FairwindsOps/polaris.svg">
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
</a>
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
<img src="https://insights.fairwinds.com/v0/gh/FairwindsOps/polaris/badge.svg">
</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 a few different modes:
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>
**Want to learn more?** Reach out on [the Slack channel](https://fairwindscommunity.slack.com/messages/polaris) ([request invite](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)), send an email to `opensource@fairwinds.com`, or join us for [office hours on Zoom](https://fairwindscommunity.slack.com/messages/office-hours)
## Documentation
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
<!-- Begin boilerplate -->
## Join the Fairwinds Open Source Community
## Integration with Fairwinds Insights
<p align="center">
<img src="https://polaris.docs.fairwinds.com/img/FW_Insights_Polaris.svg" alt="Fairwinds Insights" width="550"/>
</p>
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap,
and network with fellow Kubernetes users.
[Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)
or
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
[Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
is a platform for auditing Kubernetes clusters and enforcing policy. If you'd like to:
* manage Polaris across a fleet of clusters
* track findings over time
* send results to services like Slack and Datadog
* add additional checks from tools like
[Trivy](https://github.com/aquasecurity/trivy),
[Goldilocks](https://github.com/FairwindsOps/goldilocks/), and
[OPA](https://www.openpolicyagent.org)
<a href="https://www.fairwinds.com/t-shirt-offer?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris-tshirt">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png" alt="Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt!" />
</a>
you can sign up for a [free account here](https://insights.fairwinds.com?source=polaris).
## Other Projects from Fairwinds
## Contributing
PRs welcome! Check out the [Contributing Guidelines](https://polaris.docs.fairwinds.com/contributing) and [Code of Conduct](https://polaris.docs.fairwinds.com/code-of-conduct) for more information.
Enjoying Polaris? Check out some of our other projects:
* [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage
* [Pluto](https://github.com/FairwindsOps/Pluto) - Detect Kubernetes resources that have been deprecated or removed in future versions
* [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available
* [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters
## Further Information
A history of changes to this project can be viewed in the [Changelog](https://polaris.docs.fairwinds.com/changelog)
Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris)
## Fairwinds Insights
If you're interested in running Polaris in multiple clusters,
tracking the results over time, integrating with Slack, Datadog, and Jira,
or unlocking other functionality, check out
[Fairwinds Insights](https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris),
a platform for auditing and enforcing policy in Kubernetes clusters.
If you'd like to learn more about Polaris, or if you'd like to speak with
a Kubernetes expert, you can contact `info@fairwinds.com` or [visit our website](https://fairwinds.com)
---
<p align="center">
<img src="https://polaris.docs.fairwinds.com/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
</p>
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_Polaris_Ad.png" alt="Fairwinds Insights" />
</a>
-36
View File
@@ -1,36 +0,0 @@
successMessage: The ServiceAccount will not be automounted
failureMessage: The ServiceAccount will be automounted
category: Security
target: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required: ["serviceAccountName"]
properties:
serviceAccountName:
type: string
automountServiceAccountToken:
type: boolean
not:
const: true
additionalSchemaStrings:
ServiceAccount: |
type: object
required:
- metadata
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
- automountServiceAccountToken
{{ end }}
properties:
metadata:
type: object
required: ["name"]
properties:
name:
type: string
const: "{{ .Polaris.PodSpec.serviceAccountName }}"
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
automountServiceAccountToken:
type: boolean
const: false
{{ end }}
-55
View File
@@ -1,55 +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"
- const: "system:aggregate-to-edit"
- const: "system:controller:generic-garbage-collector"
- const: "system:controller:namespace-controller"
- 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"
- const: "system:controller:generic-garbage-collector"
- const: "system:controller:namespace-controller"
- 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") (ne .metadata.name "system:controller:generic-garbage-collector") (ne .metadata.name "system:controller:namespace-controller") }}
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"
- const: "system:controller:generic-garbage-collector"
- const: "system:controller:namespace-controller"
- 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") (ne .metadata.name "system:controller:generic-garbage-collector") (ne .metadata.name "system:controller:namespace-controller") }}
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 }}
-5
View File
@@ -25,8 +25,3 @@ schema:
type: string
not:
const: ''
mutations:
- op: add
path: /resources/limits/cpu
value: 100m
comment: "TODO: Set this to the maximum amount of CPU you want your workload to use"
-5
View File
@@ -25,8 +25,3 @@ schema:
type: string
not:
const: ''
mutations:
- op: add
path: /resources/requests/cpu
value: 100m
comment: "TODO: Set this to the amount of CPU you want to reserve for your workload"
+9 -15
View File
@@ -14,18 +14,12 @@ schema:
properties:
add:
type: array
allOf:
- not:
contains:
pattern: '^(?i)ALL$'
- not:
contains:
pattern: '^(?i)SYS_ADMIN$'
- not:
contains:
pattern: '^(?i)NET_ADMIN$'
mutations:
- op: remove
path: /securityContext/capabilities
not:
contains:
const: ALL
not:
contains:
const: SYS_ADMIN
not:
contains:
const: NET_ADMIN
+1 -4
View File
@@ -1,7 +1,7 @@
successMessage: Host IPC is not configured
failureMessage: Host IPC should not be configured
category: Security
target: PodSpec
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
@@ -9,6 +9,3 @@ schema:
hostIPC:
not:
const: true
mutations:
- op: remove
path: /hostIPC
+1 -4
View File
@@ -1,7 +1,7 @@
successMessage: Host network is not configured
failureMessage: Host network should not be configured
category: Security
target: PodSpec
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
@@ -9,6 +9,3 @@ schema:
hostNetwork:
not:
const: true
mutations:
- op: remove
path: /hostNetwork
+1 -4
View File
@@ -1,7 +1,7 @@
successMessage: Host PID is not configured
failureMessage: Host PID should not be configured
category: Security
target: PodSpec
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
@@ -9,6 +9,3 @@ schema:
hostPID:
not:
const: true
mutations:
- op: remove
path: /hostPID
-3
View File
@@ -13,6 +13,3 @@ schema:
properties:
hostPort:
const: 0
mutations:
- op: remove
path: /ports/*/hostPort
+17 -46
View File
@@ -5,56 +5,27 @@ target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- securityContext
properties:
securityContext:
type: object
required:
- capabilities
properties:
capabilities:
type: object
required:
- drop
properties:
drop:
type: array
oneOf:
- contains:
pattern: '^(?i)ALL$'
- allOf:
- contains:
pattern: '^(?i)NET_ADMIN$'
- contains:
pattern: '^(?i)CHOWN$'
- contains:
pattern: '^(?i)DAC_OVERRIDE$'
- contains:
pattern: '^(?i)FSETID$'
- contains:
pattern: '^(?i)FOWNER$'
- contains:
pattern: '^(?i)MKNOD$'
- contains:
pattern: '^(?i)NET_RAW$'
- contains:
pattern: '^(?i)SETGID$'
- contains:
pattern: '^(?i)SETUID$'
- contains:
pattern: '^(?i)SETFCAP$'
- contains:
pattern: '^(?i)SETPCAP$'
- contains:
pattern: '^(?i)NET_BIND_SERVICE$'
- contains:
pattern: '^(?i)SYS_CHROOT$'
- contains:
pattern: '^(?i)KILL$'
- contains:
pattern: '^(?i)AUDIT_WRITE$'
mutations:
- op: replace
path: /securityContext/capabilities
value: {"drop": ["ALL"]}
add:
enum:
- CHOWN
- DAC_OVERRIDE
- FSETID
- FOWNER
- MKNOD
- NET_RAW
- SETGID
- SETUID
- SETFCAP
- SETPCAP
- NET_BIND_SERVICE
- SYS_CHROOT
- KILL
- AUDIT_WRITE
-89
View File
@@ -1,89 +0,0 @@
successMessage: One of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities are used to restrict containers using unwanted privileges
FailureMessage: Use one of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities to restrict containers using unwanted privileges
category: Security
target: Container
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
definitions:
podOrContainerSeccompProfile:
type: object
{{ $podSeccompProfileType := .Polaris.PodSpec.securityContext.seccompProfile.type }}
required:
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
- securityContext
{{ end }}
properties:
securityContext:
type: object
required:
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
- seccompProfile
{{ end }}
properties:
seccompProfile:
type: object
required:
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
- type
{{ end }}
properties:
type:
type: string
allOf:
- not:
const: "Unconfined"
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
- minLength: 1
{{ end }}
podOrContainerSELinuxOptions:
type: object
{{ $podSELinuxOptions := .Polaris.PodSpec.securityContext.seLinuxOptions }}
{{ if not $podSELinuxOptions }}
required: ["securityContext"]
properties:
securityContext:
type: object
required: ["seLinuxOptions"]
properties:
seLinuxOptions:
type: object
minProperties: 1
{{ end }}
containerDropCapabilities:
type: object
required: ["securityContext"]
properties:
securityContext:
type: object
required: ["capabilities"]
properties:
capabilities:
type: object
required: ["drop"]
properties:
drop:
type: array
minItems: 1
add:
type: array
items:
type: string
not:
pattern: '^(?i)ALL$'
# End of definitions
{{/* Check for AppArmor which uses pod annotations. IF pod fields are missing,
require one of the other hardening measures. */}}
{{ $annotationName := (print "container.apparmor.security.beta.kubernetes.io/" .Polaris.Container.name) }}
{{/* Checking annotations before using index() avoids a nil panic when there are no annotations */}}
{{ $annotationExists := false }}
{{ if .Polaris.PodTemplate.metadata.annotations }}
{{ $annotationExists = index .Polaris "PodTemplate" "metadata" "annotations" $annotationName }}
{{ end }}
{{ if $annotationExists }}
type: object
{{ else }}
anyOf:
- $ref: "#/definitions/podOrContainerSeccompProfile"
- $ref: "#/definitions/podOrContainerSELinuxOptions"
- $ref: "#/definitions/containerDropCapabilities"
{{ end}}
-5
View File
@@ -19,8 +19,3 @@ schema:
type: object
not:
const: null
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"
-5
View File
@@ -25,8 +25,3 @@ schema:
type: string
not:
const: ''
mutations:
- op: add
path: /resources/limits/memory
value: "512Mi"
comment: "TODO: Set this to the maximum amount of memory you want your workload to use"
-5
View File
@@ -25,8 +25,3 @@ schema:
type: string
not:
const: ''
mutations:
- op: add
path: /resources/requests/memory
value: "512Mi"
comment: "TODO: Set this to the amount of Memory you want to reserve for your workload"
-17
View File
@@ -1,17 +0,0 @@
successMessage: Label app.kubernetes.io/name matches metadata.name
failureMessage: Label app.kubernetes.io/name must match metadata.name
target: Controller
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
required: ["labels"]
properties:
labels:
type: object
required: ["app.kubernetes.io/name"]
properties:
app.kubernetes.io/name:
const: "{{ .metadata.name }}"
-49
View File
@@ -1,49 +0,0 @@
successMessage: A NetworkPolicy matches pod labels and contains egress and ingress rules
failureMessage: A NetworkPolicy should match pod labels and contain applied egress and ingress rules
category: Security
target: PodTemplate
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
properties:
labels:
type: object
minProperties: 1
additionalSchemaStrings:
networking.k8s.io/NetworkPolicy: |
type: object
properties:
spec:
type: object
required: ["podSelector", "egress", "ingress"]
properties:
podSelector:
type: object
required: ["matchLabels"]
properties:
matchLabels:
type: object
oneOf:
{{ range $key, $value := .Polaris.PodTemplate.metadata.labels }}
- properties:
"{{ $key }}":
type: string
const: {{ $value }}
required: ["{{ $key }}"]
{{ end }}
egress:
type: array
minItems: 1
ingress:
minItems: 1
type: array
policyTypes:
type: array
allOf:
- contains:
pattern: '^(?i)Egress$'
- contains:
pattern: '^(?i)Ingress$'
-39
View File
@@ -1,39 +0,0 @@
successMessage: A PodDisruptionBudget is attached
failureMessage: Should have a PodDisruptionBudget
category: Reliability
target: Controller
controllers:
include:
- Deployment
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
properties:
labels:
type: object
minProperties: 1
additionalSchemaStrings:
policy/PodDisruptionBudget: |
type: object
properties:
spec:
type: object
required: ["selector"]
properties:
selector:
type: object
required: ["matchLabels"]
properties:
matchLabels:
type: object
anyOf:
{{ range $key, $value := .metadata.labels }}
- properties:
"{{ $key }}":
type: string
const: {{ $value }}
required: ["{{ $key }}"]
{{ end }}
@@ -19,7 +19,3 @@ schema:
replicas:
type: integer
minimum: 2
mutations:
- op: add
path: /spec/replicas
value: 3
+7 -39
View File
@@ -2,47 +2,15 @@ successMessage: Filesystem is read only
failureMessage: Filesystem should be read only
category: Security
target: Container
schemaTarget: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
goodSecurityContext:
type: object
anyOf:
- required:
- readOnlyRootFilesystem
properties:
readOnlyRootFilesystem:
const: true
notBadSecurityContext:
type: object
type: object
required:
- securityContext
properties:
securityContext:
required:
- readOnlyRootFilesystem
properties:
readOnlyRootFilesystem:
const: true
type: object
anyOf:
- required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
- properties:
containers:
type: array
items:
required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
mutations:
- op: add
path: /securityContext/readOnlyRootFilesystem
value: true
-22
View File
@@ -1,22 +0,0 @@
successMessage: Voluntary evictions are possible
failureMessage: Voluntary evictions are not possible
category: Reliability
target: policy/PodDisruptionBudget
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- spec
properties:
spec:
type: object
properties:
minAvailable:
not:
const: '100%'
maxUnavailable:
allOf:
- not:
const: 0
- not:
const: '0%'
+1 -5
View File
@@ -1,13 +1,9 @@
successMessage: Priority class has been set
failureMessage: Priority class should be set
category: Security
target: PodSpec
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- priorityClassName
mutations:
- op: add
path: /priorityClassName
value: high-priority
+5 -40
View File
@@ -2,47 +2,12 @@ successMessage: Privilege escalation not allowed
failureMessage: Privilege escalation should not be allowed
category: Security
target: Container
schemaTarget: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
goodSecurityContext:
type: object
anyOf:
- required:
- allowPrivilegeEscalation
properties:
allowPrivilegeEscalation:
const: false
notBadSecurityContext:
type: object
type: object
properties:
securityContext:
properties:
allowPrivilegeEscalation:
const: false
type: object
anyOf:
- required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
- properties:
containers:
type: array
items:
required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
mutations:
- op: add
path: /securityContext/allowPrivilegeEscalation
value: false
not:
const: true
-4
View File
@@ -9,7 +9,3 @@ schema:
properties:
imagePullPolicy:
const: Always
mutations:
- op: add
path: /imagePullPolicy
value: Always
-5
View File
@@ -19,8 +19,3 @@ schema:
type: object
not:
const: null
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"
-45
View File
@@ -1,45 +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"]
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,84 +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"
- 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" }}
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 }}
-82
View File
@@ -1,82 +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"
- 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" }}
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,78 +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"
- 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" }}
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 }}
-81
View File
@@ -1,81 +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"
- 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" }}
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 }}
+3 -18
View File
@@ -2,27 +2,12 @@ successMessage: Not running as privileged
failureMessage: Should not be running as privileged
category: Security
target: Container
schemaTarget: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
notBadSecurityContext:
type: object
type: object
properties:
securityContext:
properties:
privileged:
not:
const: true
type: object
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
mutations:
- op: add
path: /securityContext/privileged
value: false
+1 -5
View File
@@ -2,7 +2,7 @@ successMessage: Is not allowed to run as root
failureMessage: Should not be allowed to run as root
category: Security
target: Container
schemaTarget: PodSpec
schemaTarget: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
@@ -50,7 +50,3 @@ schema:
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
mutations:
- op: add
path: /securityContext/runAsNonRoot
value: true
-43
View File
@@ -1,43 +0,0 @@
successMessage: The ConfigMap does not contain potentially sensitive content in its keys and values
failureMessage: Potentially sensitive content is detected in the ConfigMap keys or values
category: Security
target: /ConfigMap
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
required: ["metadata"]
properties:
metadata:
required: ["name"]
properties:
name:
type: string
data:
type: object
propertyNames:
'$comment': These ConfigMap keys 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 ConfigMap keys not excluded above.
- pattern: '(?i).*'
additionalProperties:
'$comment': These ConfigMap 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*'
-43
View File
@@ -1,43 +0,0 @@
successMessage: The container does not set potentially sensitive environment variables
failureMessage: The container sets potentially sensitive environment variables
category: Security
target: Container
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
env:
type: array
items:
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*'
-19
View File
@@ -1,19 +0,0 @@
successMessage: Ingress has TLS configured
failureMessage: Ingress does not have TLS configured
category: Security
target: networking.k8s.io/Ingress
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- spec
properties:
spec:
type: object
required:
- tls
properties:
tls:
type: array
not:
const: null
+19 -100
View File
@@ -22,9 +22,8 @@ import (
"io/ioutil"
"net/http"
"os"
"os/exec"
cfg "github.com/fairwindsops/polaris/pkg/config"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
@@ -32,37 +31,23 @@ import (
"sigs.k8s.io/yaml"
)
var (
setExitCode bool
onlyShowFailedTests bool
minScore int
auditOutputURL string
auditOutputFile string
auditOutputFormat string
resourceToAudit string
useColor bool
helmChart string
helmValues string
checks []string
auditNamespace string
)
var setExitCode bool
var minScore int
var auditOutputURL string
var auditOutputFile string
var auditOutputFormat string
var resourceToAudit string
func init() {
rootCmd.AddCommand(auditCmd)
auditCmd.PersistentFlags().StringVar(&auditPath, "audit-path", "", "If specified, audits one or more YAML files instead of a cluster.")
auditCmd.PersistentFlags().BoolVar(&setExitCode, "set-exit-code-on-danger", false, "Set an exit code of 3 when the audit contains danger-level issues.")
auditCmd.PersistentFlags().BoolVar(&onlyShowFailedTests, "only-show-failed-tests", false, "If specified, audit output will only show failed tests.")
auditCmd.PersistentFlags().IntVar(&minScore, "set-exit-code-below-score", 0, "Set an exit code of 4 when the score is below this threshold (1-100).")
auditCmd.PersistentFlags().StringVar(&auditOutputURL, "output-url", "", "Destination URL to send audit results.")
auditCmd.PersistentFlags().StringVar(&auditOutputFile, "output-file", "", "Destination file for audit results.")
auditCmd.PersistentFlags().StringVarP(&auditOutputFormat, "format", "f", "json", "Output format for results - json, yaml, pretty, or score.")
auditCmd.PersistentFlags().BoolVar(&useColor, "color", true, "Whether to use color in pretty format.")
auditCmd.PersistentFlags().StringVarP(&auditOutputFormat, "format", "f", "json", "Output format for results - json, yaml, or score.")
auditCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
auditCmd.PersistentFlags().StringVar(&helmChart, "helm-chart", "", "Will fill out Helm template")
auditCmd.PersistentFlags().StringVar(&helmValues, "helm-values", "", "Optional flag to add helm values")
auditCmd.PersistentFlags().StringSliceVar(&checks, "checks", []string{}, "Optional flag to specify specific checks to check")
auditCmd.PersistentFlags().StringVar(&auditNamespace, "namespace", "", "Namespace to audit. Only applies to in-cluster audits")
}
var auditCmd = &cobra.Command{
@@ -73,48 +58,8 @@ var auditCmd = &cobra.Command{
if displayName != "" {
config.DisplayName = displayName
}
if len(checks) > 0 {
targetChecks := make(map[string]bool)
for _, check := range checks {
targetChecks[check] = true
}
for key := range config.Checks {
if isTarget := targetChecks[key]; !isTarget {
config.Checks[key] = cfg.SeverityIgnore
}
}
}
if auditNamespace != "" {
if helmChart != "" {
logrus.Warn("--namespace and --helm-chart are mutually exclusive. --namespace will be ignored.")
}
if auditPath != "" {
logrus.Warn("--namespace and --audit-path are mutually exclusive. --namespace will be ignored.")
}
config.Namespace = auditNamespace
}
if helmChart != "" {
var err error
auditPath, err = ProcessHelmTemplates(helmChart, helmValues)
if err != nil {
logrus.Infof("Couldn't process helm chart: %v", err)
os.Exit(1)
}
}
k, err := kube.CreateResourceProvider(context.TODO(), auditPath, resourceToAudit, config)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
os.Exit(1)
}
auditData, err := validator.RunAudit(config, k)
if err != nil {
logrus.Errorf("Error while running audit on resources: %v", err)
os.Exit(1)
}
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests)
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat)
summary := auditData.GetSummary()
score := summary.GetScore()
@@ -128,55 +73,28 @@ var auditCmd = &cobra.Command{
},
}
// ProcessHelmTemplates turns helm into yaml to be processed by Polaris or the other tools.
func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
cmd := exec.Command("helm", "dependency", "update", helmChart)
output, err := cmd.CombinedOutput()
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string) validator.AuditData {
// Create a kubernetes client resource provider
k, err := kube.CreateResourceProvider(ctx, auditPath, workload)
if err != nil {
logrus.Error(string(output))
return "", err
logrus.Errorf("Error fetching Kubernetes resources %v", err)
os.Exit(1)
}
dir, err := ioutil.TempDir("", "*")
if err != nil {
return "", err
}
params := []string{
"template", helmChart,
helmChart,
"--output-dir",
dir,
}
if helmValues != "" {
params = append(params, "--values", helmValues)
}
cmd = exec.Command("helm", params...)
output, err = cmd.CombinedOutput()
auditData, err := validator.RunAudit(ctx, c, k)
if err != nil {
logrus.Error(string(output))
return "", err
logrus.Errorf("Error while running audit on resources: %v", err)
os.Exit(1)
}
return dir, nil
}
func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFormat string, useColor bool, onlyShowFailedTests bool) {
if onlyShowFailedTests {
auditData = auditData.RemoveSuccessfulResults()
}
var outputBytes []byte
var err error
if outputFormat == "score" {
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
} else if outputFormat == "yaml" {
var jsonBytes []byte
jsonBytes, err = json.Marshal(auditData)
jsonBytes, err := json.Marshal(auditData)
if err == nil {
outputBytes, err = yaml.JSONToYAML(jsonBytes)
}
} else if outputFormat == "pretty" {
outputBytes = []byte(auditData.GetPrettyOutput(useColor))
} else {
outputBytes, err = json.MarshalIndent(auditData, "", " ")
}
@@ -230,4 +148,5 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
}
}
}
return auditData
}
+1 -3
View File
@@ -27,12 +27,10 @@ import (
var serverPort int
var basePath string
var loadAuditFile string
var listeningAddress string
func init() {
rootCmd.AddCommand(dashboardCmd)
dashboardCmd.PersistentFlags().IntVarP(&serverPort, "port", "p", 8080, "Port for the dashboard webserver.")
dashboardCmd.PersistentFlags().StringVar(&listeningAddress, "listening-address", "", "Listening Address for the dashboard webserver.")
dashboardCmd.PersistentFlags().StringVar(&basePath, "base-path", "/", "Path on which the dashboard is served.")
dashboardCmd.PersistentFlags().StringVar(&loadAuditFile, "load-audit-file", "", "Runs the dashboard with data saved from a past audit.")
dashboardCmd.PersistentFlags().StringVar(&auditPath, "audit-path", "", "If specified, audits one or more YAML files instead of a cluster.")
@@ -61,6 +59,6 @@ var dashboardCmd = &cobra.Command{
http.Handle("/", router)
logrus.Infof("Starting Polaris dashboard server on port %d", serverPort)
logrus.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", listeningAddress, serverPort), nil))
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil))
},
}
-197
View File
@@ -1,197 +0,0 @@
// Copyright 2020 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/mutation"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
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")
}
var fixCommand = &cobra.Command{
Use: "fix",
Short: "Fix Infrastructure as code files.",
Long: `Fix Infrastructure as code files.`,
Run: func(cmd *cobra.Command, args []string) {
logrus.Debug("Setting up controller manager")
if filesPath == "" {
logrus.Error("Please specify a files-path flag")
cmd.Help()
os.Exit(1)
}
var yamlFiles []string
fileInfo, err := os.Stat(filesPath)
if err != nil {
logrus.Error(err)
os.Exit(1)
}
if fileInfo.IsDir() {
baseDir := filesPath
if !strings.HasSuffix(filesPath, "/") {
baseDir = baseDir + "/"
}
yamlFiles, err = getYamlFiles(baseDir)
if err != nil {
logrus.Error(err)
os.Exit(1)
}
} else {
yamlFiles = append(yamlFiles, filesPath)
}
if len(checksToFix) > 0 {
if len(checksToFix) == 1 && checksToFix[0] == "all" {
allchecks := []string{}
for key := range config.Checks {
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 := 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)
}
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())
mutations := allMutations[key]
mutatedYamlContent, err := mutation.ApplyAllMutations(string(resource.OriginalObjectYAML), mutations)
if err != nil {
logrus.Errorf("Error applying schema mutations to the resource %s: %v", key, err)
os.Exit(1)
}
if updatedYamlContent != "" {
updatedYamlContent += "\n---\n"
}
updatedYamlContent += mutatedYamlContent
}
}
}
if isTemplate {
updatedYamlContent = retemplate(updatedYamlContent)
}
if updatedYamlContent != "" {
err = ioutil.WriteFile(fullFilePath, []byte(updatedYamlContent), 0644)
if err != nil {
logrus.Fatalf("Error writing output to file: %v", err)
}
}
}
},
}
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 {
if info.IsDir() {
return nil
}
if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
list = append(list, path)
}
return nil
})
return list, err
}
+6 -10
View File
@@ -25,11 +25,10 @@ import (
)
var configPath string
var disallowExemptions, disallowConfigExemptions, disallowAnnotationExemptions, fixChecks bool
var disallowExemptions bool
var logLevel string
var auditPath string
var displayName string
var kubeContext string
var (
version string
@@ -38,10 +37,7 @@ var (
func init() {
// Flags
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Location of Polaris configuration file.")
rootCmd.PersistentFlags().StringVarP(&kubeContext, "context", "x", "", "Set the kube context.")
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().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any exemptions from configuration file.")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level.")
flag.Parse()
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
@@ -67,10 +63,10 @@ var rootCmd = &cobra.Command{
os.Exit(1)
}
config.DisallowExemptions = disallowExemptions
config.DisallowConfigExemptions = disallowConfigExemptions
config.DisallowAnnotationExemptions = disallowAnnotationExemptions
config.KubeContext = kubeContext
if disallowExemptions {
config.DisallowExemptions = true
}
},
Run: func(cmd *cobra.Command, args []string) {
logrus.Error("You must specify a sub-command.")
+9 -20
View File
@@ -16,6 +16,7 @@ package cmd
import (
"os"
"time"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -23,22 +24,16 @@ import (
fwebhook "github.com/fairwindsops/polaris/pkg/webhook"
k8sConfig "sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
)
var webhookPort int
var disableWebhookConfigInstaller bool
var enableMutations bool
var enableValidations bool
var certDir string
func init() {
rootCmd.AddCommand(webhookCmd)
webhookCmd.PersistentFlags().IntVarP(&webhookPort, "port", "p", 9876, "Port for the dashboard webserver.")
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")
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.")
}
var webhookCmd = &cobra.Command{
@@ -49,7 +44,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,25 +52,19 @@ 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()
server.CertName = "tls.crt"
server.KeyName = "tls.key"
if !enableMutations && !enableValidations {
logrus.Errorf("One of --mutate or --validate must be set to true")
os.Exit(1)
}
// Iterate all the configurations supported controllers to scan and register them for webhooks
// Should only register controllers that are configured to be scanned
fwebhook.NewWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
if enableValidations {
fwebhook.NewValidateWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
}
if enableMutations {
fwebhook.NewMutateWebhook(mgr, fwebhook.Mutator{Config: config, Client: mgr.GetClient()})
}
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
logrus.Errorf("Error starting manager: %v", err)
+2
View File
@@ -0,0 +1,2 @@
coverage:
range: 50...80
+146
View File
@@ -0,0 +1,146 @@
---
# Source: polaris/templates/0-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: polaris
---
# Source: polaris/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: polaris
namespace: polaris
labels:
app: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: polaris
labels:
app: polaris
rules:
# required by controller-runtime code doing a cluster wide lookup
# when it seems namespace would suffice
- apiGroups:
- ''
resources:
- 'nodes'
verbs:
- 'get'
- 'list'
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: polaris-view
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: polaris
namespace: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: polaris
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: polaris
subjects:
- kind: ServiceAccount
name: polaris
namespace: polaris
---
# Source: polaris/templates/dashboard.service.yaml
apiVersion: v1
kind: Service
metadata:
name: polaris-dashboard
namespace: polaris
labels:
app: polaris
annotations:
spec:
ports:
- name: http-dashboard
port: 80
protocol: TCP
targetPort: 8080
selector:
app: polaris
component: dashboard
type: ClusterIP
---
# Source: polaris/templates/dashboard.deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: polaris-dashboard
namespace: polaris
labels:
app: polaris
component: dashboard
spec:
replicas: 1
selector:
matchLabels:
app: polaris
component: dashboard
template:
metadata:
labels:
app: polaris
component: dashboard
spec:
containers:
- command:
- polaris
- dashboard
image: 'quay.io/fairwinds/polaris:3.0'
imagePullPolicy: 'Always'
name: dashboard
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 20
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 20
resources:
limits:
cpu: 150m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
serviceAccountName: polaris
nodeSelector:
tolerations:
+262
View File
@@ -0,0 +1,262 @@
---
# Source: polaris/templates/0-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: polaris
---
# Source: polaris/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: polaris
namespace: polaris
labels:
app: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: polaris
labels:
app: polaris
rules:
# required by controller-runtime code doing a cluster wide lookup
# when it seems namespace would suffice
- apiGroups:
- ''
resources:
- 'nodes'
verbs:
- 'get'
- 'list'
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: polaris-view
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: polaris
namespace: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: polaris
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: polaris
subjects:
- kind: ServiceAccount
name: polaris
namespace: polaris
---
# Source: polaris/templates/webhook.service.yaml
apiVersion: v1
kind: Service
metadata:
name: polaris-webhook
namespace: polaris
labels:
app: polaris
spec:
ports:
- name: webhook
port: 443
protocol: TCP
targetPort: 9876
selector:
app: polaris
component: webhook
type: ClusterIP
---
# Source: polaris/templates/webhook.deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: polaris-webhook
namespace: polaris
labels:
app: polaris
component: webhook
spec:
replicas: 1
selector:
matchLabels:
app: polaris
component: webhook
template:
metadata:
labels:
app: polaris
component: webhook
spec:
containers:
- name: webhook
command:
- polaris
- webhook
image: 'quay.io/fairwinds/polaris:3.0'
imagePullPolicy: 'Always'
ports:
- containerPort: 9876
# These are fairly useless readiness/liveness probes for now
# Follow this issue for potential improvements:
# https://github.com/kubernetes-sigs/controller-runtime/issues/356
livenessProbe:
exec:
command:
- sh
- -c
- ps -ef | grep polaris
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
exec:
command:
- sh
- -c
- ps -ef | grep polaris
initialDelaySeconds: 5
periodSeconds: 5
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
volumeMounts:
- name: secret
mountPath: /opt/cert/
readOnly: true
- name: cr-logs
mountPath: /tmp/
readOnly: false
serviceAccountName: polaris
nodeSelector:
tolerations:
volumes:
- name: secret
secret:
secretName: polaris
- name: cr-logs
emptyDir: {}
---
# Source: polaris/templates/webhook.cert.yaml
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: polaris-cert
namespace: polaris
labels:
app: polaris
spec:
commonName: polaris-webhook.polaris.svc
dnsNames:
- polaris-webhook.polaris.svc
- polaris-webhook.polaris
- polaris-webhook
- polaris-webhook.polaris.svc.
issuerRef:
kind: Issuer
name: polaris-selfsigned
secretName: polaris
---
# Source: polaris/templates/webhook.cert.yaml
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: polaris-selfsigned
namespace: polaris
spec:
selfSigned: {}
---
# Source: polaris/templates/webhook.configuration.yaml
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: polaris-webhook
annotations:
cert-manager.io/inject-ca-from: polaris/polaris-cert
webhooks:
- admissionReviewVersions:
- v1beta1
clientConfig:
service:
name: polaris-webhook
namespace: polaris
path: /validate
port: 443
failurePolicy: Fail
matchPolicy: Exact
name: polaris.fairwinds.com
namespaceSelector:
matchExpressions:
- key: control-plane
operator: DoesNotExist
objectSelector:
{}
rules:
- apiGroups:
- apps
apiVersions:
- v1
- v1beta1
- v1beta2
operations:
- CREATE
- UPDATE
resources:
- daemonsets
- deployments
- statefulsets
scope: Namespaced
- apiGroups:
- batch
apiVersions:
- v1
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- jobs
- cronjobs
scope: Namespaced
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods
- replicationcontrollers
scope: Namespaced
sideEffects: None
timeoutSeconds: 10
-7
View File
@@ -33,13 +33,6 @@ module.exports = {
"/infrastructure-as-code",
],
},
{
title: "Usage",
collapsable: false,
children: [
"/cli",
],
},
{
title: "Customization",
collapsable: false,
+1 -1
View File
@@ -42,7 +42,7 @@ const baseConfig = {
],
themeConfig: {
docsRepo: "",
docsDir: 'docs',
docsDir: 'docs-md',
editLinks: true,
editLinkText: "Help us improve this page",
logo: '/img/fairwinds-logo.svg',
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

+10 -33
View File
@@ -14,39 +14,16 @@ var sf14gv = 32793;
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version=2.0;
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window,document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '521127644762074');
fbq('track', 'PageView');
s.parentNode.insertBefore(t,s)}(window, document,script,
https://connect.facebook.net/en_US/fbevents.js);
fbq(init, 159554595936922);
fbq(track, PageView);
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
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');
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({gtm.start:
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-K5KK5H3);
-7
View File
@@ -23,13 +23,6 @@
color: $successColor;
}
blockquote {
border-left: 0.2rem solid $warningColor;
}
blockquote p {
color: $warningColor;
}
.theme-default-content:not(.custom),
.page-nav,
.page-edit,
+2 -5
View File
@@ -3,7 +3,7 @@
<br>
<h3>Best Practices for Kubernetes Workload Configuration</h3>
<a href="https://github.com/FairwindsOps/polaris">
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.1.6&color=239922">
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.0.0&color=239922">
</a>
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
@@ -11,14 +11,11 @@
<a href="https://circleci.com/gh/FairwindsOps/polaris.svg">
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
</a>
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
<img src="https://insights.fairwinds.com/v0/gh/FairwindsOps/polaris/badge.svg">
</a>
</div>
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.
problems in the future. Polaris can be run in a few different modes:
Polaris can be run in three different modes:
* As a [dashboard](/dashboard), so you can audit what's running inside your cluster.
+6 -20
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Documentation: Polaris can be run as an admission controller that acts as a validating webhook."
---
# Admission Controller
> Want to manage the Admission Controller across multiple clusters? Check out
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
@@ -25,10 +20,15 @@ If you don't use cert-manager, you'll need to:
* Create a TLS secret in your cluster with a valid certificate that uses that CA
* Pass the name of that secret with the webhook.secretName parameter.
### kubectl
```bash
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/webhook.yaml
```
### Helm
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace \
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris \
--set webhook.enable=true --set dashboard.enable=false
```
@@ -45,17 +45,3 @@ output unless we are rejecting a workload altogether.
This means that any checks with a severity of `warning` will still pass webhook validation,
and the only evidence of that warning will either be in the Polaris dashboard or the
Polaris webhook logs. This will change in a future version of Kubernetes.
## Mutating Webhook
By default, the Admission Controller is just pass/fail, but
Polaris can also operate as a mutating webhook for many of the issues it checks for.
This means Polaris will remediate the issue it finds, rather than rejecting
the deployment.
To enable the mutating webhook, add `--set webhook.mutate=true` to your
Helm instlallation command.
By default, the only mutation enabled is `pullPolicyNotAlways`. If you'd like to
enable other mutations, you can set the `webhook.mutations` flag.
-102
View File
@@ -1,108 +1,6 @@
---
sidebarDepth: 0
meta:
- name: description
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
## 7.0.0
* Better support for `polaris fix`
* `target: Pod` is now `target: PodSpec` (to differentiate naked Pods from Controllers)
## 6.0.0
* Preliminary support for `polaris fix` command
* Changes to how Pod owners are determined
* Removed YAML manifests from the `deploy/` directory - Helm is now the default install mechanism
## 5.1.0
* Support `--context` flag for kubecontext
* Treat core Kubernetes controllers (`Deployments`, `StatefulSets`, `DaemonSets`, `CronJobs`) as top-level objects, instead of following owner refs
## 5.0.0
* Renamed `multipleReplicasForDeployment` to `deploymentMissingReplicas`
* Changed `RunAsRootAllowed` and `hostNetworkSet` default severity to `danger`
* Changed `deploymentMissingReplicas` default severity to `warning`
## 4.2.0
* New flags `--disallow-(config|annotation)-exemptions`
* Kubernetes dependency updates
* Documentation updates
## 4.1.0
* Handle case-insentitivity in capabilities checks
* Change test for PDB disruptions to better handle IaC
## 4.0.9
* Update Alpine base image
## 4.0.8
* Fix support for namespace checks
## 4.0.7
* Fix goreleaser format
## 4.0.6
* Change goreleaser format
* Fix `--helm-values` flag
## 4.0.5
* Bugfix for repeated objects on the dashboard
## 4.0.4
* Bugfix for validating webhook and non-pod checks
## 4.0.3
* Fixed bad interaction between `--set-exit-score-below` and `--only-show-failed-tests`
* Dependency updates
* Support for Helm chart scanning
## 4.0.2
* Goreleaser fix
## 4.0.1
* Goreleaser fix
## 4.0.0
* Add support for arbitrary resources, like Ingress or PodDisruptionBudget
* Add support check templating (see docs)
* Add support for multi-resource checks (see docs)
### Breaking Changes
* In custom checks, `jsonSchema` is now `schemaString`
* Check `pdbDisruptionsAllowedGreaterThanZero` is now called `pdbDisruptionsIsZero`
## 3.2.0
* Add `--format=pretty` option for CLI output
## 3.1.6
* Fix nil pointer issue with --only-output-failed-tests
## 3.1.5
* Fix UI display of Ingress checks
## 3.1.4
* Fixes for exemption annotations for the admission controller
## 3.1.3
* Fixes for `privilegeEscalationAllowed` and `insecureCapabilities` checks to take Kubernetes defaults into account
## 3.1.2
* Start checking deployment configuration using Fairwinds Insights
## 3.1.1
* Updated to alpine:3.13
## 3.1.0
* Added support for Ingress objects
* Fixes for exemptions, including support for exempting entire namespaces
## 3.0.0
* **Breaking** - fixed inconsistency in how controller-level checks are handled
Custom checks with `target: Controller` should remove `Object` from the top-level of the
-5
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Ensure that CPU and memory settings are configured, so that Kubernetes can schedule your workload effectively"
---
# Efficiency
These checks ensure that CPU and memory settings are configured, so that
+1 -7
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Make sure your Kubernetes workloads are always available, and are running the correct image."
---
# Reliability
These checks help to make sure your workloads are always available,
@@ -15,8 +10,7 @@ key | default | description
`tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
`deploymentMissingReplicas` | `warning` | Fails when there is only one replica for a deployment.
`missingPodDisruptionBudget` | `ignore`
`multipleReplicasForDeployment` | `ignore` | Fails when there is only one replica for a deployment.
## Background
-5
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Security Checks Documentation"
---
# Security
These checks are related to security concerns. Workloads that fail these
-61
View File
@@ -1,61 +0,0 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Learn your CLI options"
---
#### CLI Options
```
# top-level commands
audit
Runs a one-time audit.
dashboard
Runs the webserver for Polaris dashboard.
help
Prints help, if you give it a command then it will print help for that command. Same as -h
version
Prints the version of Polaris
webhook
Runs the webhook webserver
# global flags
-c, --config string Location of Polaris configuration file.
-x, --context string Set the kube context.
--disallow-exemptions Disallow any exemptions from configuration file.
--disallow-config-exemptions Disallow exemptions set within the configuration file.
--disallow-annotation-exemptions Disallow any exemption defined as a controller annotation.
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
--log-level string Logrus log level. (default "info")
# dashboard flags
--audit-path string If specified, audits one or more YAML files instead of a cluster.
--base-path string Path on which the dashboard is served. (default "/")
--display-name string An optional identifier for the audit.
-h, --help help for dashboard
--listening-address string Listening Address for the dashboard webserver.
--load-audit-file string Runs the dashboard with data saved from a past audit.
-p, --port int Port for the dashboard webserver. (default 8080)
# audit flags
--audit-path string If specified, audits one or more YAML files instead of a cluster.
--checks stringArray Optional flag to specify specific checks to check
--color Whether to use color in pretty format. (default true)
--display-name string An optional identifier for the audit.
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
--helm-chart string Will fill out Helm template
--helm-values string Optional flag to add helm values
-h, --help help for audit
--namespace string Namespace to audit. Only applies to in-cluster audits
--only-show-failed-tests If specified, audit output will only show failed tests.
--output-file string Destination file for audit results.
--output-url string Destination URL to send audit results.
--resource string Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
# webhook flags
--disable-webhook-config-installer disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.
-h, --help help for webhook
-p, --port int Port for the dashboard webserver. (default 9876)
```
+66
View File
@@ -0,0 +1,66 @@
#### CLI Options
```
# top-level commands
audit
Runs a one-time audit.
dashboard
Runs the webserver for Polaris dashboard.
help
Prints help, if you give it a command then it will print help for that command. Same as -h
version
Prints the version of Polaris
webhook
Runs the webhook webserver
# high-level flags
-c, --config string
Location of Polaris configuration file
--disallow-exemptions
Disallow any exemptions from configuration file.
-h, --help
Help for Polaris (same as help command)
--kubeconfig string
Path to a kubeconfig. Only required if out-of-cluster.
--log-level string
Logrus log level (default "info")
--master string
The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.
# dashboard flags
--audit-path string
If specified, audits one or more YAML files instead of a cluster
--base-path string
Path on which the dashboard is served (default "/")
--display-name string
An optional identifier for the audit
--load-audit-file string
Runs the dashboard with data saved from a past audit.
-p, --port int
Port for the dashboard webserver (default 8080)
# audit flags
--audit-path string
If specified, audits one or more YAML files instead of a cluster
--resource string
If specified, audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend
--display-name string
An optional identifier for the audit
--format string
Output format for results - json, yaml, or score (default "json")
--output-file string
Destination file for audit results
--output-url string
Destination URL to send audit results
--set-exit-code-below-score int
Set an exit code of 4 when the score is below this threshold (1-100)
--set-exit-code-on-danger
Set an exit code of 3 when the audit contains danger-level issues.
# webhook flags
--disable-webhook-config-installer
disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
-p, --port int
Port for the webhook webserver (default 9876)
```
-5
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Contribution Code of Conduct"
---
# Code of Conduct
## Our Pledge
+49 -24
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Contribution Guidelines"
---
# Contributing
Issues, whether bugs, tasks, or feature requests are essential for keeping Polaris great. We believe it should be as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can keep on top of things.
@@ -37,26 +32,12 @@ We label issues with the ["good first issue" tag](https://github.com/FairwindsOp
The following commands are all required to pass as part of Polaris testing:
```bash
```
go list ./... | grep -v vendor | xargs golint -set_exit_status
go list ./... | grep -v vendor | xargs go vet
go test ./pkg/... -v -coverprofile cover.out
```
### Webhook tests
```bash
kind create cluster --wait=90s --image kindest/node:v1.15.11 --name polaris-test
docker build -t quay.io/fairwinds/polaris:debug . # or use your own registry
docker push quay.io/fairwinds/polaris:debug
helm repo add jetstack https://charts.jetstack.io
kubectl create ns cert-manager
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
POLARIS_IMAGE=quay.io/fairwinds/polaris:debug ./test/webhook_test.sh
```
to avoid the final cleanup for debugging purposes, you can run
```bash
SKIP_FINAL_CLEANUP=true IMAGE_TAG=debug ./test/webhook_test.sh
```
## Creating a New Issue
If you've encountered an issue that is not already reported, please create a [new issue](https://github.com/FairwindsOps/polaris/issues), choose `Bug Report`, `Feature Request` or `Misc.` and follow the instructions in the template.
@@ -69,11 +50,55 @@ Each new pull request should:
- Reference any related issues
- Add tests that show the issues have been solved
- Pass existing tests and linting
- Contain a clear indication of if they're ready for review, or a work in progress
- Contain a clear indication of if they're ready for review or a work in progress
- Be up to date and/or rebased on the master branch
## Creating a new release
To create a new release, simply tag this repo with the new version.
For major and minor releases, don't forget to update the Helm chart at
https://github.com/FairwindsOps/charts
### Patch releases
Patch releases only need to change this repo. The Helm chart and deploy scripts
will automatically pull in the latest changes.
If the release involves changes to anything in the `deploy/` folder (e.g. new RBAC permissions),
it needs to be a minor or major release in order to prevent breaking the Helm chart.
1. Create a PR for this repo
1. Bump the version number in:
1. main.go
2. README.md
2. Update CHANGELOG.md
3. Merge your PR
2. Tag the latest branch for this repo
1. Pull the latest commit for the `master` branch (which you just merged in your PR)
2. Run `git tag $VERSION && git push --tags`
3. Make sure CircleCI runs successfully for the new tag - this will push images to quay.io and create a release in GitHub
1. If CircleCI fails, check with Codeowners ASAP
### Minor/Major releases
Minor and major releases need to change both this repository and the
[Helm chart repo](https://github.com/FairwindsOps/charts/).
The steps are:
1. Modify the [Helm chart](https://github.com/FairwindsOps/charts/stable/polaris)
1. Clone the helm charts repo
1. `git clone https://github.com/FairwindsOps/charts`
2. `git checkout -b yourname/update-polaris`
1. Bump the version number in `stable/polaris/Chart.yaml`
2. Make any necessary changes to the chart to support the new version of Polaris (e.g. new RBAC permissions)
3. **Don't merge yet!**
2. Create a PR for this repo
1. Create a new branch named `yourname/update-version`
2. Bump the version number in:
1. main.go
2. README.md
3. Regenerate the deployment files. Assuming you've cloned the charts repo to `~/git/charts`:
1. `CHARTS_DIR=~/git/charts ./scripts/generate-deployment-files.sh`
4. Update CHANGELOG.md
5. Merge your PR
3. Tag the latest branch for this repo
1. Pull the latest for the `master` branch
2. Run `git tag $VERSION && git push --tags`
3. Make sure CircleCI runs successfully for the new tag - this will push images to quay.io and create a release in GitHub
1. If CircleCI fails, check with Codeowners ASAP
4. Create and merge a PR for your changes to the Helm chart
-5
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Documentation on setting checks by severity "
---
# Check Settings
Each check can be assigned a `severity`. Only checks with a severity of `danger` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `danger` will result in a change being rejected.
-5
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Learn about Polaris defaults and how to customize configurations. "
---
# Configuration
The default Polaris configuration can be [seen here](https://github.com/FairwindsOps/polaris/blob/master/examples/config.yaml).
+9 -198
View File
@@ -1,29 +1,16 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Documentation: Create your own checks, you can use JSON Schema"
---
# Custom Checks
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/).
This is how built-in Polaris checks are defined as well - you can see all the built-in checks
in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/checks) for examples.
If you write a check that could be useful for others, feel free to open a PR to add it in!
## Basic Example
For example, to disallow images from quay.io:
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/). For example,
to disallow images from quay.io:
```yaml
checks:
imageRegistry: warning
customChecks:
imageRegistry:
successMessage: Image comes from allowed registries
failureMessage: Image should not be from disallowed registry
category: Security
target: Container
category: Images
target: Container # target can be "Container" or "Pod"
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
@@ -34,187 +21,6 @@ customChecks:
pattern: ^quay.io
```
## Available Options
All custom checks should go under the `customChecks` field in your Polaris config, keyed by the
check ID. Note that you'll also have to set its severity in the `checks` section of your Polaris config.
* `successMessage` - the message to show when the check succeeds
* `failureMessage` - the message to show when the check fails
* `category` - one of `Security`, `Efficiency`, or `Reliability`
* `target` - specifies the type of resource to check. This can be:
* a group and kind, e.g. `apps/Deployment` or `networking.k8s.io/Ingress`
* `Controller`, to check _any_ resource that creates Pods (e.g. Deployments, CronJobs, StatefulSets), as well as naked Pods
* `PodTemplate`, same as `Controller`, but the schema applies to the Pod template rather than the top-level controller
* `PodSpec`, same as `Controller`, but the schema applies to the Pod spec rather than the top-level controller
* `Container` same as `Controller`, but the schema applies to all Container specs rather than the top-level controller
* `controllers` - if `target` is `Controller`, `PodSpec` or `Container`, you can use this to change which types of controllers are checked
* `controllers.include` - _only_ check these controllers
* `controllers.exclude` - check all controllers except these
* `containers` - if `target` is `Container`, you can use this to decide if `initContainers`, `containers`, or both should be checked
* `containers.exclude` - can be set to a list including `initContainer` or `container`
* `schema` - the JSON Schema to check against, as a YAML object
* `schemaString` - this JSON Schema to check against, as a YAML or JSON string. See [Templating](#templating) below
* Note: only _one_ of `schema` and `schemaString` can be specified.
* `additionalSchemas` - see [Multi-Resource Checks](#multi-resource-checks) below
* `additionalSchemaStrings` - see [Multi-Resource Checks](#multi-resource-checks) below
* Note: only _one_ of `additionalSchemas` and `additionalSchemaStrings` can be specified.
## Checking CPU and Memory
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
strings like `1000m` and `1G`. Here's an example check that memory and CPU falls within a certain range.
```yaml
customChecks:
resourceLimits:
containers:
exclude:
- initContainer
successMessage: Resource limits are within the required range
failureMessage: Resource limits should be within the required range
category: Resources
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- limits
properties:
limits:
type: object
required:
- memory
- cpu
properties:
memory:
type: string
resourceMinimum: 100M
resourceMaximum: 6G
cpu:
type: string
resourceMinimum: 100m
resourceMaximum: "2"
```
## Resource Presence
You can test for the presence of a resource in each Namespace. For example, to
ensure an AlertmanagerConfig is in every Namespace:
```yaml
successMessage: Namespace has monitoring configuration
failureMessage: Namespace should have monitoring configuration
category: Security
target: Namespace
schema: {}
additionalSchemas:
monitoring.coreos.com/AlertmanagerConfig: {}
```
## Templating
You can also utilize go templating in your JSON schema in order to match one field against another.
E.g. here is the built-in check to ensure that the `name` annotation matches the object's name:
```yaml
successMessage: Label app.kubernetes.io/name matches metadata.name
failureMessage: Label app.kubernetes.io/name must match metadata.name
target: Controller
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
required: ["labels"]
properties:
labels:
type: object
required: ["app.kubernetes.io/name"]
properties:
app.kubernetes.io/name:
const: "{{ .metadata.name }}"
```
* The object available via the go template is the full object, and not limited by `target`.
* A check of `target: PodSpec` can directly access the pod specification via the go template variable `.Polaris.PodSpec`.
* A check of `target: PodTemplate` can directly access the pod template via the go template variable `.Polaris.PodTemplate`.
* A check of `target: Container` can directly access the container being checked via the go template variable `.Polaris.container`. The pod template and pod specification can also be accessed via the respective variables `.Polaris.PodTemplate` and `.Polaris.PodSpec`. Access to pod-level fields allows a container check to consult related fields from the pod, such as `securityContext`.
You can also use the full [Go template syntax](https://golang.org/pkg/text/template/), though
you may need to specify your schema as a string in order to use concepts like `range`. E.g.
this check ensures that at least one of the object's labels is present in `matchLabels`:
```yaml
schemaString: |
type: object
properties:
spec:
type: object
required: ["selector"]
properties:
selector:
type: object
required: ["matchLabels"]
properties:
matchLabels:
type: object
anyOf:
{{ range $key, $value := .metadata.labels }}
- properties:
"{{ $key }}":
type: string
const: {{ $value }}
required: ["{{ $key }}"]
{{ end }}
```
## 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.
Here's the check to ensure that every Deployment has a PDB:
```yaml
successMessage: A PodDisruptionBudget is attached
failureMessage: Should have a PodDisruptionBudget
category: Reliability
target: Controller
controllers:
include:
- Deployment
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
properties:
labels:
type: object
minProperties: 1
additionalSchemaStrings:
policy/PodDisruptionBudget: |
type: object
properties:
spec:
type: object
required: ["selector"]
properties:
selector:
type: object
required: ["matchLabels"]
properties:
matchLabels:
type: object
anyOf:
{{ range $key, $value := .metadata.labels }}
- properties:
"{{ $key }}":
type: string
const: {{ $value }}
required: ["{{ $key }}"]
{{ end }}
```
## JSON vs YAML
Schemas can also be specified as JSON strings instead of YAML, for easier copy/pasting:
```yaml
customChecks:
@@ -226,3 +32,8 @@ customChecks:
}
```
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
strings like `1000m` and `1G`. You can see an example in [the extended config](https://github.com/FairwindsOps/polaris/tree/master/examples/config-full.yaml)
There are additional examples in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/checks).
+4 -29
View File
@@ -1,17 +1,9 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Exemptions"
---
# Exemptions
Sometimes a workload really does need to do things that Polaris considers insecure. For instance,
many of the `kube-system` workloads need to run as root, or need access to the host network. In these
cases, we can add **exemptions** to allow the workload to pass Polaris checks.
Exemptions can be added in a few different ways:
- Namespace: By editing the Polaris config.
- Controller: By annotating a controller, or editing the Polaris config.
- Container: By editing the Polaris config.
Exemptions can be added two ways: by annotating a controller, or editing the Polaris config.
## Annotations
To exempt a controller from all checks via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g.
@@ -26,36 +18,19 @@ kubectl annotate deployment my-deployment polaris.fairwinds.com/cpuRequestsMissi
## Config
To add exemptions via the config, you have to specify at least one or more of the following:
- A namespace
- A list of controller names
- A list of container names
You can also specify a list of particular rules. If no rules are specified then every rule is exempted.
Controller names and container names are matched as a prefix, so an empty string will match every controller or container respectively.
For example:
To exempt a controller via the config, you have to specify a namespace (optional), a list of controller names and a list of rules, e.g.
```yaml
exemptions:
# exemption valid for all rules on all containers in all controllers in default namespace
- namespace: default
# exemption valid for hostNetworkSet rule on all containers in dns-controller controller in kube-system namespace
# exemption valid for kube-system namespace
- namespace: kube-system
controllerNames:
- dns-controller
rules:
- hostNetworkSet
# exemption valid for hostNetworkSet rule on all containers in dns-controller controller in all namespaces
# exemption valid in all namespaces
- controllerNames:
- dns-controller
rules:
- hostNetworkSet
# exemption valid for hostNetworkSet rule on coredns container in all controllers in kube-system namespace
- namespace: kube-system
- containerNames:
- coredns
rules:
- hostNetworkSet
```
+6 -6
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Ways to run Polaris | The Polaris dashboard can be installed on a cluster using kubectl or Helm"
---
# Dashboard
> Want to see Polaris results for all your clusters in a single dashboard? Check out
@@ -16,10 +11,15 @@ don't conform to best practices.
## Installation
### kubectl
```bash
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/dashboard.yaml
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
### Helm
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
+2 -104
View File
@@ -1,8 +1,3 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Documentation | Polaris can be used on the command line to audit local Kubernetes manifests stored in YAML files."
---
# Infrastructure as Code
> Want to see results for all your IaC repos in one place? Check out
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
@@ -25,36 +20,9 @@ brew install FairwindsOps/tap/polaris
polaris version
```
## Checking Infrastructure as Code files
You can audit Kubernetes YAML files by running:
```bash
polaris audit --audit-path ./deploy/ --format=pretty
```
This will print out any issues Polaris finds in your manifests.
Polaris can only check raw YAML manifests. If you'd like to check a Helm template,
you can run `helm template` to generate a manifest that Polaris can check.
## Fixing Issues
Polaris can automatically fix many of the issues it finds. For example, you can run
```bash
polaris fix --files-path ./deploy/ --checks=all
```
to fix any issues inside the `deploy` directory. Polaris may leave
comments next to some changes (e.g. liveness and readiness probes) prompting
the user to set them to something more appropriate given the context of their
application.
Note that not all issues can be automatically fixed.
Currently only raw YAML manifests can be mutated. Helm charts etc.
still need to be changed manually.
## Running in a CI pipeline
### Set minimum score for an exit code
You can tell the CLI to set an exit code if it detects certain issues with your YAML files.
You can tell the CLI to set an exit code if it detects certain issues with your
YAML files.
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
```bash
polaris audit --audit-path ./deploy/ \
@@ -62,73 +30,3 @@ polaris audit --audit-path ./deploy/ \
--set-exit-code-below-score 90
```
### Pretty-print results
By default, results are output as JSON. You can get human-readable output with
the `--format=pretty` flag:
```bash
polaris audit --audit-path ./deploy/ \
--format=pretty
```
You can also disable colors and emoji:
```bash
polaris audit --audit-path ./deploy/ \
--format=pretty \
--color=false
```
### Output only showing failed tests
The CLI to gives you ability to display results containing only failed tests.
For example:
```bash
polaris audit --audit-path ./deploy/ \
--only-show-failed-tests true
```
### Audit Helm Charts
You can audit helm charts using the `--helm-chart` and `--helm-values` flags:
```
polaris audit \
--helm-chart ./deploy/chart \
--helm-values ./deploy/chart/values.yml
```
### As Github Action
#### Setup polaris action
This action downloads a version of [polaris](https://github.com/FairwindsOps/polaris) and adds it to the path. It makes the [polaris cli](https://polaris.docs.fairwinds.com/infrastructure-as-code) ready to use in following steps of the same job.
##### Inputs
###### `version`
The release version to fetch. This has to be in the form `<tag_name>`.
##### Outputs
###### `version`
The version number of the release tag.
##### Example usage
```yaml
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
version: 5.0.0
```
Example inside a job:
```yaml
steps:
- uses: actions/checkout@v2
- name: Setup polaris
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
version: 5.0.0
- name: Use command
run: polaris version
```
-5
View File
@@ -1,5 +0,0 @@
---
meta:
- name: description
content: "Fairwinds Polaris | Documentation"
---
+2434 -18833
View File
File diff suppressed because it is too large Load Diff
+3 -4
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"
@@ -26,8 +26,7 @@
"scripts": {
"build": "npm run build:readme && npm run build:docs",
"build:docs": "vuepress build -d ../dist/",
"build:metadata": "cat main-metadata.md > README.md || true",
"build:readme": "npm run build:metadata && cat ../README.md | grep -v 'ocumentation' | sed \"s/https:\\/\\/\\w\\+.docs.fairwinds.com//g\" >> README.md",
"build:readme": "cat ../README.md | grep -v 'ocumentation' | sed \"s/https:\\/\\/\\w\\+.docs.fairwinds.com//g\" > README.md",
"check-links": "vuepress check-md",
"serve": "npm run build:readme && vuepress dev --port 3003",
"vuepress": "vuepress"
+4 -14
View File
@@ -1,6 +1,6 @@
checks:
# reliability
deploymentMissingReplicas: warning
multipleReplicasForDeployment: warning
priorityClassNotSet: warning
tagNotSpecified: danger
pullPolicyNotAlways: warning
@@ -12,31 +12,20 @@ checks:
memoryRequestsMissing: warning
memoryLimitsMissing: warning
# security
automountServiceAccountToken: warning
hostIPCSet: danger
hostPIDSet: danger
linuxHardening: danger
missingNetworkPolicy: warning
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: danger
runAsRootAllowed: warning
runAsPrivileged: danger
dangerousCapabilities: danger
insecureCapabilities: warning
hostNetworkSet: danger
hostNetworkSet: warning
hostPortSet: warning
sensitiveContainerEnvVar: danger
sensitiveConfigmapContent: danger
clusterrolePodExecAttach: danger
rolePodExecAttach: danger
clusterrolebindingPodExecAttach: danger
rolebindingClusterRolePodExecAttach: danger
rolebindingRolePodExecAttach: danger
# custom
resourceLimits: warning
imageRegistry: danger
exemptions:
- controllerNames:
- my-network-controller
@@ -97,3 +86,4 @@ customChecks:
type: string
not:
pattern: ^quay.io
+3 -11
View File
@@ -1,15 +1,11 @@
checks:
# reliability
deploymentMissingReplicas: warning
multipleReplicasForDeployment: ignore
priorityClassNotSet: ignore
tagNotSpecified: danger
pullPolicyNotAlways: warning
readinessProbeMissing: warning
livenessProbeMissing: warning
metadataAndNameMismatched: ignore
pdbDisruptionsIsZero: warning
missingPodDisruptionBudget: ignore
# efficiency
cpuRequestsMissing: warning
cpuLimitsMissing: warning
@@ -20,16 +16,12 @@ checks:
hostPIDSet: danger
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: danger
runAsRootAllowed: warning
runAsPrivileged: danger
dangerousCapabilities: danger
insecureCapabilities: warning
hostNetworkSet: danger
hostNetworkSet: warning
hostPortSet: warning
tlsSettingsMissing: warning
mutations:
- pullPolicyNotAlways
exemptions:
- namespace: kube-system
-7
View File
@@ -1,7 +0,0 @@
options:
organization: fairwinds-production
baseBranch: master
images:
docker:
- quay.io/fairwinds/polaris:$CI_SHA1
+33 -88
View File
@@ -1,95 +1,40 @@
module github.com/fairwindsops/polaris
go 1.19
go 1.13
require (
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.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.25.0
k8s.io/apimachinery v0.25.0
k8s.io/client-go v0.25.0
sigs.k8s.io/controller-runtime v0.13.0
sigs.k8s.io/yaml v1.3.0
)
require (
github.com/pkg/errors v0.9.1
gomodules.xyz/jsonpatch/v2 v2.2.0
)
require (
cloud.google.com/go/compute v1.9.0 // 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/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.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.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.5.4 // indirect
github.com/go-logr/logr v1.2.3 // 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.22.3 // indirect
github.com/gobuffalo/logger v1.0.7 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // 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
cloud.google.com/go v0.65.0 // indirect
github.com/Azure/go-autorest/autorest v0.11.4 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.2 // indirect
github.com/gobuffalo/packr/v2 v2.8.1
github.com/google/go-cmp v0.5.2 // 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.0.1 // 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
github.com/mailru/easyjson v0.7.7 // indirect
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.16 // 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/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/gophercloud/gophercloud v0.12.0 // indirect
github.com/gorilla/mux v1.8.0
github.com/imdario/mergo v0.3.11 // indirect
github.com/karrick/godirwalk v1.16.1 // indirect
github.com/kr/pretty v0.2.0 // indirect
github.com/prometheus/common v0.13.0 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/component-base v0.25.0 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect
k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
github.com/qri-io/jsonschema v0.1.1
github.com/rogpeppe/go-internal v1.6.2 // indirect
github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 // indirect
gitlab.com/golang-commonmark/markdown v0.0.0-20191127184510-91b5b3c99c19
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
gomodules.xyz/jsonpatch/v2 v2.1.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
k8s.io/api v0.18.8
k8s.io/apimachinery v0.18.8
k8s.io/client-go v0.18.6
k8s.io/klog/v2 v2.1.0 // indirect
k8s.io/utils v0.0.0-20200821003339-5e75c0163111 // indirect
sigs.k8s.io/controller-runtime v0.6.4
sigs.k8s.io/yaml v1.2.0
)
+514 -342
View File
File diff suppressed because it is too large Load Diff
+4 -6
View File
@@ -15,14 +15,12 @@
package main
import (
cmd "github.com/fairwindsops/polaris/cmd/polaris"
"github.com/fairwindsops/polaris/cmd/polaris"
)
var (
// Version is set during build
Version = "development"
// Commit is set during build
Commit = ""
const (
// Version represents the current release version of Polaris
Version = "3.0.0"
)
func main() {
-87
View File
@@ -1,87 +0,0 @@
// Copyright 2022 FairwindsOps, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"github.com/gobuffalo/packr/v2"
"github.com/sirupsen/logrus"
)
var (
// BuiltInChecks contains the checks that come pre-installed w/ Polaris
BuiltInChecks = map[string]SchemaCheck{}
schemaBox = (*packr.Box)(nil)
// We explicitly set the order to avoid thrash in the
// tests as we migrate toward JSON schema
checkOrder = []string{
// Controller Checks
"deploymentMissingReplicas",
// Pod checks
"hostIPCSet",
"hostPIDSet",
"hostNetworkSet",
"automountServiceAccountToken",
// Container checks
"memoryLimitsMissing",
"memoryRequestsMissing",
"cpuLimitsMissing",
"cpuRequestsMissing",
"readinessProbeMissing",
"livenessProbeMissing",
"pullPolicyNotAlways",
"tagNotSpecified",
"hostPortSet",
"runAsRootAllowed",
"runAsPrivileged",
"notReadOnlyRootFilesystem",
"privilegeEscalationAllowed",
"dangerousCapabilities",
"insecureCapabilities",
"priorityClassNotSet",
"linuxHardening",
"sensitiveContainerEnvVar",
// Other checks
"tlsSettingsMissing",
"pdbDisruptionsIsZero",
"metadataAndNameMismatched",
"missingPodDisruptionBudget",
"missingNetworkPolicy",
"sensitiveConfigmapContent",
"clusterrolePodExecAttach",
"rolePodExecAttach",
"clusterrolebindingPodExecAttach",
"rolebindingClusterRolePodExecAttach",
"rolebindingRolePodExecAttach",
"clusterrolebindingClusterAdmin",
"rolebindingClusterAdminClusterRole",
"rolebindingClusterAdminRole",
}
)
func init() {
schemaBox = packr.New("Schemas", "../../checks")
for _, checkID := range checkOrder {
contents, err := schemaBox.Find(checkID + ".yaml")
if err != nil {
panic(err)
}
check, err := ParseCheck(checkID, contents)
if err != nil {
logrus.Errorf("Error while parsing check %s", checkID)
panic(err)
}
BuiltInChecks[checkID] = check
}
}
+11 -17
View File
@@ -23,30 +23,24 @@ import (
"net/http"
"strings"
"github.com/gobuffalo/packr/v2"
packr "github.com/gobuffalo/packr/v2"
"k8s.io/apimachinery/pkg/util/yaml"
)
// Configuration contains all of the config for the validation checks.
type Configuration struct {
DisplayName string `json:"displayName"`
Checks map[string]Severity `json:"checks"`
CustomChecks map[string]SchemaCheck `json:"customChecks"`
Exemptions []Exemption `json:"exemptions"`
DisallowExemptions bool `json:"disallowExemptions"`
DisallowConfigExemptions bool `json:"disallowConfigExemptions"`
DisallowAnnotationExemptions bool `json:"disallowAnnotationExemptions"`
Mutations []string `json:"mutations"`
KubeContext string `json:"kubeContext"`
Namespace string `json:"namespace"`
DisplayName string `json:"displayName"`
Checks map[string]Severity `json:"checks"`
CustomChecks map[string]SchemaCheck `json:"customChecks"`
Exemptions []Exemption `json:"exemptions"`
DisallowExemptions bool `json:"disallowExemptions"`
}
// Exemption represents an exemption to normal rules
type Exemption struct {
Rules []string `json:"rules"`
ControllerNames []string `json:"controllerNames"`
ContainerNames []string `json:"containerNames"`
Namespace string `json:"namespace"`
Namespace string `json:"namespace"`
}
var configBox = (*packr.Box)(nil)
@@ -65,14 +59,14 @@ func ParseFile(path string) (Configuration, error) {
if path == "" {
rawBytes, err = getConfigBox().Find("config.yaml")
} else if strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://") {
// path is a url
//path is a url
response, err2 := http.Get(path)
if err2 != nil {
return Configuration{}, err2
}
rawBytes, err = ioutil.ReadAll(response.Body)
} else {
// path is local
//path is local
rawBytes, err = ioutil.ReadFile(path)
}
if err != nil {
@@ -108,8 +102,8 @@ func Parse(rawBytes []byte) (Configuration, error) {
}
// Validate checks if a config is valid
func (conf Configuration) Validate() error {
if len(conf.Checks) == 0 {
func (c Configuration) Validate() error {
if len(c.Checks) == 0 {
return errors.New("No checks were enabled")
}
return nil
+6 -12
View File
@@ -16,7 +16,6 @@ package config
import (
"context"
"fmt"
"io"
"log"
"net/http"
@@ -143,9 +142,7 @@ func TestConfigNoServerError(t *testing.T) {
func TestConfigWithCustomChecks(t *testing.T) {
valid := map[string]interface{}{
"securityContext": map[string]interface{}{
"foo": "bar",
},
"securityContext": map[string]interface{}{},
}
invalid := map[string]interface{}{
"notSecurityContext": map[string]interface{}{},
@@ -154,23 +151,20 @@ func TestConfigWithCustomChecks(t *testing.T) {
parsedConf, err := Parse([]byte(confCustomChecks))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]interface{}{})
isValid, _, err := check.CheckObject(valid)
isValid, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
assert.NoError(t, err)
assert.Equal(t, true, isValid)
isValid, _, err = check.CheckObject(invalid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(valid)
assert.NoError(t, err)
if !assert.Equal(t, true, isValid) {
fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
}
isValid, _, err = check.CheckObject(invalid)
assert.Equal(t, true, isValid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
}
+16 -43
View File
@@ -1,68 +1,41 @@
// Copyright 2022 FairwindsOps, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// IsActionable determines whether a check is actionable given the current configuration
func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, containerName string) bool {
func (conf Configuration) IsActionable(ruleID, namespace, controllerName string) bool {
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
return false
}
if conf.DisallowExemptions || conf.DisallowConfigExemptions {
if conf.DisallowExemptions {
return true
}
for _, exemption := range conf.Exemptions {
if exemption.Namespace != "" && exemption.Namespace != objMeta.GetNamespace() {
for _, example := range conf.Exemptions {
if example.Namespace != "" && example.Namespace != namespace {
continue
}
checkIfRuleMatches := false
for _, rule := range exemption.Rules {
for _, rule := range example.Rules {
if rule != ruleID {
continue
}
checkIfRuleMatches = true
break
}
if len(exemption.Rules) == 0 || checkIfRuleMatches {
if !isExemptionCheckMatched(exemption.ControllerNames, objMeta.GetName()) {
continue
for _, controller := range example.ControllerNames {
if strings.HasPrefix(controllerName, controller) {
return false
}
}
if isExemptionCheckMatched(exemption.ContainerNames, containerName) {
return false
}
if len(example.Rules) == 0 {
for _, controller := range example.ControllerNames {
if strings.HasPrefix(controllerName, controller) {
return false
}
}
}
}
return true
}
func isExemptionCheckMatched(arr []string, predicate string) bool {
if len(arr) == 0 {
return true
}
for _, container := range arr {
if strings.HasPrefix(predicate, container) {
return true
}
}
return false
}
+46 -231
View File
@@ -18,248 +18,63 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var confContainerTest = `
var confExemptRuleTest = `
checks:
deploymentMissingReplicas: warning
priorityClassNotSet: warning
pullPolicyNotAlways: warning
ANY: warning
OTHER: warning
exemptions:
- namespace: prometheus
rules:
- deploymentMissingReplicas
- controllerNames:
- controller2
rules:
- deploymentMissingReplicas
- namespace: kube-system
controllerNames:
- controller3
rules:
- deploymentMissingReplicas
- containerNames:
- container41
- container42
rules:
- deploymentMissingReplicas
- namespace: kube-system
containerNames:
- container51
- container52
rules:
- deploymentMissingReplicas
- controllerNames:
- controller6
containerNames:
- container61
- container62
- test
rules:
- deploymentMissingReplicas
- namespace: kube-system
controllerNames:
- controller7
containerNames:
- container71
- container72
rules:
- deploymentMissingReplicas
- priorityClassNotSet
- namespace: polaris
- ANY
`
func createMeta(namespace, name string) metav1.Object {
unst := unstructured.Unstructured{}
obj, err := meta.Accessor(&unst)
if err != nil {
panic(err)
}
obj.SetName(name)
obj.SetNamespace(namespace)
return obj
var confExemptTest = `
checks:
ANY: warning
exemptions:
- controllerNames:
- test
`
var confNamespaceTest = `
checks:
ANY: warning
exemptions:
- namespace: kube-system
controllerNames:
- test
`
func TestInclusiveExemption(t *testing.T) {
parsedConf, _ := Parse([]byte(confExemptTest))
applicable := parsedConf.IsActionable("ANY", "test", "test")
applicableOtherController := parsedConf.IsActionable("ANY","test", "other")
assert.False(t, applicable, "Expected all checks to be exempted when their controller is specified.")
assert.True(t, applicableOtherController, "Expected checks to only be exempted when their controller is specified.")
}
func TestNamespaceExemptionForSpecifiedRules(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
func TestIndividualRuleException(t *testing.T) {
parsedConf, _ := Parse([]byte(confExemptRuleTest))
applicable := parsedConf.IsActionable("ANY", "test", "test")
applicableOtherRule := parsedConf.IsActionable("OTHER","test", "test")
applicableOtherRuleOtherController := parsedConf.IsActionable("OTHER","test", "other")
applicableRuleOtherController := parsedConf.IsActionable("ANY","test", "other")
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", ""), "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller1"), "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", ""), "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller1"), "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("prometheus", "controller1"), "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "")
assert.True(t, actionable)
assert.False(t, applicable, "Expected all checks to be exempted when their controller and rule are specified.")
assert.True(t, applicableOtherRule, "Expected checks to only be exempted when their controller and rule are specified.")
assert.True(t, applicableOtherRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
assert.True(t, applicableRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
}
func TestNamespaceExemptionForAllRules(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
func TestNamespaceExemption(t *testing.T) {
parsedConf, _ := Parse([]byte(confNamespaceTest))
applicable := parsedConf.IsActionable("ANY", "kube-system", "test")
applicableOtherController := parsedConf.IsActionable("ANY","default", "test")
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", ""), "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", "controller1"), "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", ""), "container11")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", "controller1"), "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("polaris", "controller1"), "")
assert.False(t, actionable)
}
func TestControllerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller2"), "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller2"), "container21")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller2"), "container21")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller2"), "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller3"), "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller3"), "")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller3"), "container31")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller4"), "")
assert.True(t, actionable)
}
func TestOnlyContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container42")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller4"), "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller4"), "container41")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container51")
assert.True(t, actionable)
}
func TestNamespaceAndContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container51")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", ""), "container51")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller5"), "container51")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller5"), "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("insights-agent", ""), "container51")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container51")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller5"), "container51")
assert.True(t, actionable)
}
func TestControllerAndContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller6"), "container61")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("", "controller6"), "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "container61")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller7"), "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container61")
assert.True(t, actionable)
}
func TestContainerExemption(t *testing.T) {
parsedConf, err := Parse([]byte(confContainerTest))
assert.NoError(t, err)
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller7"), "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "container71")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("insights-agent", "controller7"), "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "container71")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "container61")
assert.True(t, actionable)
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", "controller7"), "container71")
assert.False(t, actionable)
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("kube-system", "controller8"), "container71")
assert.True(t, actionable)
}
assert.False(t, applicable, "Expected all checks to be exempted when their namespace and controller is specified.")
assert.True(t, applicableOtherController, "Expected checks to only be exempted when their namespace and controller is specified.")
}
+30 -198
View File
@@ -1,113 +1,43 @@
// Copyright 2022 FairwindsOps, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"text/template"
"github.com/qri-io/jsonschema"
"github.com/thoas/go-funk"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
)
// TargetKind represents the part of the config to be validated
type TargetKind string
const (
// TargetController points to the controller's spec
TargetController TargetKind = "Controller"
// TargetContainer points to the container spec
TargetContainer TargetKind = "Container"
// TargetPodSpec points to the pod spec
TargetPodSpec TargetKind = "PodSpec"
// TargetPodTemplate points to the pod template
TargetPodTemplate TargetKind = "PodTemplate"
// TargetPod points to the pod spec
TargetPod TargetKind = "Pod"
// TargetController points to the controller's spec
TargetController TargetKind = "Controller"
)
// HandledTargets is a list of target names that are explicitly handled
var HandledTargets = []TargetKind{
TargetController,
TargetContainer,
TargetPodSpec,
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
}
// SchemaCheck is a Polaris check that runs using JSON Schema
type SchemaCheck struct {
ID string `yaml:"id" json:"id"`
Category string `yaml:"category" json:"category"`
SuccessMessage string `yaml:"successMessage" json:"successMessage"`
FailureMessage string `yaml:"failureMessage" json:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers" json:"controllers"`
Containers includeExcludeList `yaml:"containers" json:"containers"`
Target TargetKind `yaml:"target" json:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget" json:"schemaTarget"`
Schema map[string]interface{} `yaml:"schema" json:"schema"`
SchemaString string `yaml:"schemaString" json:"schemaString"`
Validator jsonschema.RootSchema `yaml:"-" json:"-"`
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"`
ID string `yaml:"id"`
Category string `yaml:"category"`
SuccessMessage string `yaml:"successMessage"`
FailureMessage string `yaml:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers"`
Containers includeExcludeList `yaml:"containers"`
Target TargetKind `yaml:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget"`
Schema jsonschema.RootSchema `yaml:"schema"`
JSONSchema string `yaml:"jsonSchema"`
}
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 {
reader := bytes.NewReader(raw)
d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
if err := d.Decode(dest); err != nil {
if err == io.EOF {
break
}
return fmt.Errorf("Decoding schema check failed: %v", err)
}
}
return nil
}
// ParseCheck parses a check from a byte array
func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
check := SchemaCheck{}
err := UnmarshalYAMLOrJSON(rawBytes, &check)
if err != nil {
return check, err
}
check.Initialize(id)
return check, nil
}
func init() {
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
@@ -187,146 +117,48 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
// Initialize sets up the schema
func (check *SchemaCheck) Initialize(id string) error {
check.ID = id
if check.SchemaString == "" {
jsonBytes, err := json.Marshal(check.Schema)
if err != nil {
if check.JSONSchema != "" {
if err := json.Unmarshal([]byte(check.JSONSchema), &check.Schema); err != nil {
return err
}
check.SchemaString = string(jsonBytes)
}
if check.AdditionalSchemaStrings == nil {
check.AdditionalSchemaStrings = make(map[string]string)
}
for kind, schema := range check.AdditionalSchemas {
jsonBytes, err := json.Marshal(schema)
if err != nil {
return err
}
check.AdditionalSchemaStrings[kind] = string(jsonBytes)
}
check.Schema = map[string]interface{}{}
check.AdditionalSchemas = map[string]map[string]interface{}{}
return nil
}
// TemplateForResource fills out a check's templated fields given a particular resource
func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, error) {
newCheck := check // Make a copy of the check, since we're going to modify the schema
templateStrings := map[string]string{
"": newCheck.SchemaString,
}
for kind, schema := range newCheck.AdditionalSchemaStrings {
templateStrings[kind] = schema
}
newCheck.SchemaString = ""
newCheck.AdditionalSchemaStrings = map[string]string{}
for kind, tmplString := range templateStrings {
tmpl := template.New(newCheck.ID)
tmpl, err := tmpl.Parse(tmplString)
if err != nil {
return nil, err
}
w := bytes.Buffer{}
err = tmpl.Execute(&w, res)
if err != nil {
return nil, err
}
if kind == "" {
newCheck.SchemaString = w.String()
} else {
newCheck.AdditionalSchemaStrings[kind] = w.String()
}
}
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
val := jsonschema.RootSchema{}
err := UnmarshalYAMLOrJSON([]byte(schemaStr), &val)
if err != nil {
return nil, err
}
newCheck.AdditionalValidators[kind] = val
}
err := UnmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
if err != nil {
return nil, err
}
return &newCheck, err
}
// CheckPodSpec checks a pod spec against the schema
func (check SchemaCheck) CheckPodSpec(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
// CheckPod checks a pod spec against the schema
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
return check.CheckObject(pod)
}
// CheckPodTemplate checks a pod template against the schema
func (check SchemaCheck) CheckPodTemplate(podTemplate interface{}) (bool, []jsonschema.ValError, error) {
return check.CheckObject(podTemplate)
}
// CheckController checks a controler's spec against the schema
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
errs, err := check.Validator.ValidateBytes(bytes)
return len(errs) == 0, errs, err
func (check SchemaCheck) CheckController(bytes []byte) (bool, error) {
errs, err := check.Schema.ValidateBytes(bytes)
return len(errs) == 0, err
}
// CheckContainer checks a container spec against the schema
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
return check.CheckObject(container)
}
// CheckObject checks arbitrary data against the schema
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
bytes, err := json.Marshal(obj)
if err != nil {
return false, nil, err
return false, err
}
errs, err := check.Validator.ValidateBytes(bytes)
return len(errs) == 0, errs, err
}
// CheckAdditionalObjects looks for an object that passes the specified additional schema
func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []interface{}) (bool, error) {
val, ok := check.AdditionalValidators[groupkind]
if !ok {
return false, errors.New("No validator found for " + groupkind)
}
for _, obj := range objects {
bytes, err := json.Marshal(obj)
if err != nil {
return false, err
}
errs, err := val.ValidateBytes(bytes)
if err != nil {
return false, err
}
if len(errs) == 0 {
return true, nil
}
}
return false, nil
errs, err := check.Schema.ValidateBytes(bytes)
return len(errs) == 0, err
}
// IsActionable decides if this check applies to a particular target
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
if funk.Contains(HandledTargets, target) {
if check.Target == TargetPodTemplate && target == TargetPodSpec {
// A target=PodSpec and check.Target=PodTemplate is expected
// because applyPodSchemaChecks() explicitly sets check.Target
return true
}
if check.Target != target {
return false
}
} else if string(check.Target) != kind && !strings.HasSuffix(string(check.Target), "/"+kind) {
func (check SchemaCheck) IsActionable(target TargetKind, namespace, controllerType string, isInit bool) bool {
if check.Target != target {
return false
}
isIncluded := len(check.Controllers.Include) == 0
for _, inclusion := range check.Controllers.Include {
if inclusion == kind {
if inclusion == controllerType {
isIncluded = true
break
}
@@ -335,7 +167,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit boo
return false
}
for _, exclusion := range check.Controllers.Exclude {
if exclusion == kind {
if exclusion == controllerType {
return false
}
}

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