Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e91b9b8824 | ||
|
|
d695a2041e | ||
|
|
19d3af9056 | ||
|
|
31e9f22072 | ||
|
|
675eb15603 | ||
|
|
5e1f758943 | ||
|
|
a7aef2a595 | ||
|
|
fa2438248e | ||
|
|
6bebdb8e17 | ||
|
|
40fbe9cab0 | ||
|
|
be80d91a5c | ||
|
|
975b829f0a | ||
|
|
0e2212dcaa | ||
|
|
94e74e3d80 | ||
|
|
008a7cbe9f | ||
|
|
8584110e73 | ||
|
|
6d89e6bab6 | ||
|
|
1731febeb1 | ||
|
|
268f0e6811 | ||
|
|
e31f3f1b41 | ||
|
|
5acdc4a4b9 | ||
|
|
0f1d4cd952 | ||
|
|
c398f1043c | ||
|
|
091fa77d11 | ||
|
|
1b97f31edb | ||
|
|
8c454fa733 | ||
|
|
32c1150b28 | ||
|
|
19341205b7 | ||
|
|
89dfce5bbd | ||
|
|
a62389b85f | ||
|
|
5aa397ca22 | ||
|
|
d41ce88f16 | ||
|
|
310015ff53 | ||
|
|
bb7ba83362 | ||
|
|
c0d8eb6318 | ||
|
|
19bf91e13b | ||
|
|
9ae4f774e9 | ||
|
|
6c14d968c9 | ||
|
|
3e655d846b | ||
|
|
0232e31a50 | ||
|
|
a396f049d0 | ||
|
|
a55acdd372 | ||
|
|
ca3e46ebbd | ||
|
|
e661e9542d | ||
|
|
b923caf79e | ||
|
|
3e79863aa7 | ||
|
|
d172d8e18b | ||
|
|
38c410c5aa | ||
|
|
653eac6002 | ||
|
|
6c995eb04c | ||
|
|
e52f1e76e9 | ||
|
|
cf69099d3a | ||
|
|
20f1be3975 | ||
|
|
f602687c90 | ||
|
|
a3404a132d | ||
|
|
46836ccc4c | ||
|
|
ff8e66af07 | ||
|
|
01cc5fa642 | ||
|
|
cd47487b0b | ||
|
|
7e7e553c0d | ||
|
|
8385fd10e5 | ||
|
|
ccc795d58e | ||
|
|
0007b12080 | ||
|
|
2958b5cc07 | ||
|
|
0c5014489e | ||
|
|
ebf99869ff | ||
|
|
a43a0fe2f5 | ||
|
|
88b57f9f32 | ||
|
|
e028c34448 | ||
|
|
7c1ec6a762 | ||
|
|
fa6843ae61 | ||
|
|
76cc9e3296 | ||
|
|
6bcdb834ed | ||
|
|
2bac1c9ba9 | ||
|
|
0e6ca81f57 | ||
|
|
4bc2522e25 | ||
|
|
d4bb6239c7 | ||
|
|
082e9c0406 | ||
|
|
178742e0f3 | ||
|
|
1651aa7004 | ||
|
|
e6c9e4558e | ||
|
|
b6f1bdf098 | ||
|
|
cbc15ad069 | ||
|
|
68d295ec6b | ||
|
|
90339357c2 | ||
|
|
2e7368d011 | ||
|
|
6cd65fbed8 | ||
|
|
1ede736971 | ||
|
|
1935abd563 | ||
|
|
842ccf4853 | ||
|
|
2c56a313a1 | ||
|
|
7b00d73a6f | ||
|
|
a995e7e724 | ||
|
|
3211d6d25d | ||
|
|
d011bb454a | ||
|
|
8e62436697 | ||
|
|
c9811171ce | ||
|
|
30eebaf16a | ||
|
|
dbf001fa53 | ||
|
|
f753fc91f2 | ||
|
|
239a321588 | ||
|
|
61eb1f95a4 | ||
|
|
a66a6b89da | ||
|
|
46923d0a0a | ||
|
|
ba1b4d5db9 | ||
|
|
ba0a25acf9 | ||
|
|
371e30fe3d | ||
|
|
1013834e3c | ||
|
|
e802c28801 | ||
|
|
14d503580b | ||
|
|
0fc109c199 | ||
|
|
63fd576d3e | ||
|
|
f0c8ee256e | ||
|
|
d5cb68084e | ||
|
|
f7d2309608 | ||
|
|
d800e8d629 | ||
|
|
eebc9537a2 | ||
|
|
1fed099b53 | ||
|
|
25dfae1fea | ||
|
|
c365fb08cf | ||
|
|
768b715fdf | ||
|
|
7d938ac405 | ||
|
|
2f5964519a | ||
|
|
2064384985 | ||
|
|
b436699260 | ||
|
|
1a025da649 | ||
|
|
4c3d0e0603 | ||
|
|
714b7bfbba | ||
|
|
16ffe1e1c4 | ||
|
|
7f1c143d77 | ||
|
|
f42af35352 | ||
|
|
8bbe13b6fa | ||
|
|
dea7143827 | ||
|
|
41d5f72822 | ||
|
|
3e49a3af98 | ||
|
|
89ff4a6d53 | ||
|
|
a5852f3003 | ||
|
|
4a04999ce7 | ||
|
|
c16aac808f | ||
|
|
0aa173789a | ||
|
|
38b5cd8193 | ||
|
|
ef59f7c26e | ||
|
|
08900887e4 | ||
|
|
9ddbe5a329 | ||
|
|
3f7fccf5f1 | ||
|
|
d9e148c0f2 | ||
|
|
613c4b9ea1 | ||
|
|
fe0060af77 | ||
|
|
7b428fe230 | ||
|
|
2e148546d8 | ||
|
|
23d76594a6 | ||
|
|
512a5f2886 | ||
|
|
09d5fdcd1e | ||
|
|
e4d6fb9b61 | ||
|
|
c11b839009 | ||
|
|
6cda7cd7d5 | ||
|
|
381c24e940 | ||
|
|
a8e901639e | ||
|
|
93a80e44d3 | ||
|
|
bc866a4d18 | ||
|
|
ec557f7ce8 | ||
|
|
4cee8b7e35 | ||
|
|
ba53a2b8ab | ||
|
|
3a8655de81 | ||
|
|
3f62126bdd | ||
|
|
ee8768591b | ||
|
|
6e93b799b1 | ||
|
|
17d19cacff | ||
|
|
f5a1ad61bd | ||
|
|
8840f0dc5b | ||
|
|
5ab9f0b251 | ||
|
|
a540a7359a | ||
|
|
fc368485ef | ||
|
|
8f510a1b67 | ||
|
|
9d68ee2359 | ||
|
|
ef4f5aeaeb | ||
|
|
a79260a324 | ||
|
|
0c398d21f2 | ||
|
|
f1957631b5 | ||
|
|
86b3ab5186 | ||
|
|
a1c131f922 | ||
|
|
54aacb786f | ||
|
|
564803c9f8 | ||
|
|
f421d7e5a8 | ||
|
|
5f793beb73 | ||
|
|
5fd9e01493 | ||
|
|
a4e45a0e95 | ||
|
|
dd2976794a | ||
|
|
edf28c8790 | ||
|
|
7c98598858 | ||
|
|
2393f0bbf9 | ||
|
|
4e044602f4 | ||
|
|
fdd30717e5 | ||
|
|
272e06bbec | ||
|
|
e57668fc75 | ||
|
|
9dd7f0947a | ||
|
|
ca6e4b43e4 | ||
|
|
3a2fb3584b | ||
|
|
550447234a | ||
|
|
717d9b2630 | ||
|
|
6b7ccdadc4 | ||
|
|
b511abab60 | ||
|
|
fab5cb9a21 | ||
|
|
949eb9e04a | ||
|
|
e4656bcae8 | ||
|
|
f62454df8d | ||
|
|
7374d8b5dd | ||
|
|
99b9c0af27 | ||
|
|
fbff67c7eb | ||
|
|
6fc1ee0adb | ||
|
|
2bf5a195c1 | ||
|
|
2d326419c2 | ||
|
|
31fc0ab6d9 | ||
|
|
7e2bd017b3 | ||
|
|
5d6446afed | ||
|
|
b1ead662a2 | ||
|
|
bf9173acea | ||
|
|
568a79b4f6 | ||
|
|
e64fb4cdbd | ||
|
|
2b77ac2b1e | ||
|
|
20ea63ebe5 | ||
|
|
11d5200962 | ||
|
|
aefad52704 | ||
|
|
86b856a88c | ||
|
|
33dcb3f267 | ||
|
|
7c9ab1395f | ||
|
|
1a8fb44b78 | ||
|
|
51d91c7d2e | ||
|
|
0cd0d83172 | ||
|
|
b3f1b3b478 | ||
|
|
4ff34de49b | ||
|
|
5bce1db05e | ||
|
|
b27f619d5e |
@@ -17,6 +17,7 @@ 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}
|
||||
|
||||
install_k8s: &install_k8s
|
||||
run:
|
||||
@@ -45,16 +46,19 @@ references:
|
||||
|
||||
kubectl version
|
||||
|
||||
# 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
|
||||
echo "Installing Helm"
|
||||
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
|
||||
chmod 700 get_helm.sh
|
||||
./get_helm.sh
|
||||
|
||||
echo "Installing cert-manager"
|
||||
kubectl create namespace cert-manager
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
helm repo update
|
||||
echo "Helm install"
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
|
||||
echo "Install cert-manager successful"
|
||||
|
||||
test_binary_dashboard: &test_binary_dashboard
|
||||
run:
|
||||
name: Test Dashboard
|
||||
@@ -83,8 +87,8 @@ references:
|
||||
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 -
|
||||
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.174.2/goreleaser_amd64.deb
|
||||
echo "bad33997ea9977a84196bdca1d5993fada909cd81c3e88d52bd297666bea61a4 goreleaser.deb" | sha256sum -c -
|
||||
sudo dpkg -i goreleaser.deb
|
||||
rm goreleaser.deb
|
||||
|
||||
@@ -144,22 +148,36 @@ jobs:
|
||||
test:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
- image: circleci/golang:1.16
|
||||
steps:
|
||||
- checkout
|
||||
- *set_environment_variables
|
||||
- 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 test ./... -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: Adjust configs for latest image
|
||||
command: |
|
||||
sed -r "s|'(quay.io/fairwinds/polaris:).+'|'\1${CIRCLE_SHA1}'|" ./deploy/webhook.yaml > ./deploy/dashboard.yaml
|
||||
sed -r "s|'(quay.io/fairwinds/polaris:).+'|'\1${CIRCLE_SHA1}'|" ./deploy/dashboard.yaml > ./deploy/webhook.yaml
|
||||
- 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/
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
- image: circleci/golang:1.16
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
@@ -181,6 +199,31 @@ jobs:
|
||||
- *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
|
||||
|
||||
@@ -197,6 +240,12 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
- insights:
|
||||
requires:
|
||||
- push
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
- test_k8s:
|
||||
requires:
|
||||
- push
|
||||
@@ -211,7 +260,6 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# Testing tags are reserved for testing circle test + build steps
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- release_images:
|
||||
@@ -221,6 +269,11 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# Testing tags are reserved for testing circle test + build steps
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- publish_docs:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
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.
|
||||
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
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
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
|
||||
|
||||
-->
|
||||
@@ -6,5 +6,3 @@ labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# 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"]
|
||||
@@ -0,0 +1,22 @@
|
||||
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'
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,44 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: "11:00"
|
||||
ignore:
|
||||
- dependency-name: cloud.google.com/go
|
||||
versions:
|
||||
- ">= 0.57.a, < 0.58"
|
||||
- dependency-name: github.com/go-logr/logr
|
||||
versions:
|
||||
- ">= 0.2.a, < 0.3"
|
||||
- dependency-name: github.com/go-logr/zapr
|
||||
versions:
|
||||
- ">= 0.2.a, < 0.3"
|
||||
- dependency-name: github.com/googleapis/gnostic
|
||||
versions:
|
||||
- ">= 0.4.a, < 0.5"
|
||||
- dependency-name: github.com/googleapis/gnostic
|
||||
versions:
|
||||
- ">= 0.5.a, < 0.6"
|
||||
- dependency-name: github.com/qri-io/jsonschema
|
||||
versions:
|
||||
- ">= 0.2.a, < 0.3"
|
||||
- dependency-name: k8s.io/api
|
||||
versions:
|
||||
- ">= 0.19.a, < 0.20"
|
||||
- dependency-name: k8s.io/apimachinery
|
||||
versions:
|
||||
- ">= 0.19.a, < 0.20"
|
||||
- dependency-name: k8s.io/client-go
|
||||
versions:
|
||||
- ">= 0.19.a, < 0.20"
|
||||
- dependency-name: sigs.k8s.io/controller-runtime
|
||||
versions:
|
||||
- ">= 0.5.a, < 0.6"
|
||||
- dependency-name: sigs.k8s.io/controller-runtime
|
||||
versions:
|
||||
- ">= 0.8.a, < 0.9"
|
||||
- dependency-name: k8s.io/apimachinery
|
||||
versions:
|
||||
- 0.20.4
|
||||
@@ -0,0 +1,13 @@
|
||||
This PR fixes issue #
|
||||
|
||||
## 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?
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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
|
||||
@@ -0,0 +1,27 @@
|
||||
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
|
||||
@@ -24,3 +24,6 @@ dist
|
||||
.vscode
|
||||
|
||||
*-test.yaml
|
||||
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
@@ -7,21 +7,28 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
builds:
|
||||
- id: watcher
|
||||
- id: polaris
|
||||
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 }}"
|
||||
brews:
|
||||
- name: polaris
|
||||
github:
|
||||
tap:
|
||||
owner: FairwindsOps
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
* @rbren @makoscafee @jordandoig @baderbuddy
|
||||
## DO NOT EDIT - Managed by Terraform
|
||||
* @rbren @makoscafee
|
||||
|
||||
@@ -55,7 +55,7 @@ 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 [INSERT EMAIL ADDRESS]. All
|
||||
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.
|
||||
@@ -71,4 +71,4 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.13 AS build-env
|
||||
FROM golang:1.16 AS build-env
|
||||
WORKDIR /go/src/github.com/fairwindsops/polaris/
|
||||
|
||||
ENV GO111MODULE=on
|
||||
@@ -15,7 +15,7 @@ RUN go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
COPY . .
|
||||
RUN packr2 build -a -o polaris *.go
|
||||
|
||||
FROM alpine:3.10
|
||||
FROM alpine:3.14
|
||||
WORKDIR /usr/local/bin
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
|
||||
@@ -1,84 +1,66 @@
|
||||
<div align="center">
|
||||
<img src="/img/polaris-logo.png" alt="Polaris Logo" />
|
||||
<div align="center" class="no-border">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/polaris-logo.png" alt="Polaris Logo">
|
||||
<br>
|
||||
|
||||
[![Version][version-image]][version-link] [![CircleCI][circleci-image]][circleci-link] [![Go Report Card][goreport-image]][goreport-link]
|
||||
<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=5.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">
|
||||
<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>
|
||||
|
||||
[version-image]: https://img.shields.io/static/v1.svg?label=Version&message=1.2.0&color=239922
|
||||
[version-link]: https://github.com/FairwindsOps/polaris
|
||||
|
||||
[goreport-image]: https://goreportcard.com/badge/github.com/FairwindsOps/polaris
|
||||
[goreport-link]: https://goreportcard.com/report/github.com/FairwindsOps/polaris
|
||||
|
||||
[circleci-image]: https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg
|
||||
[circleci-link]: https://circleci.com/gh/FairwindsOps/polaris.svg
|
||||
|
||||
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:
|
||||
problems in the future.
|
||||
|
||||
Polaris can be run in three different modes:
|
||||
* As a [dashboard](#dashboard), so you can audit what's running inside your cluster.
|
||||
* As a [validating webhook](#webhook), so you can automatically reject workloads that don't adhere to your organization's policies.
|
||||
* As a [command-line tool](#cli), so you can test local YAML files, e.g. as part of a CI/CD process.
|
||||
|
||||
**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)
|
||||
|
||||
# Dashboard Quickstart
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://github.com/FairwindsOps/polaris/releases/latest/download/dashboard.yaml
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
With the port forwarding in place, you can open http://localhost:8080 in your browser to view the dashboard.
|
||||
|
||||
* * *
|
||||
|
||||
# Components
|
||||
|
||||
## Dashboard
|
||||
> [View installation instructions](docs/usage.md#dashboard)
|
||||
|
||||
The Polaris dashboard is a way to get a simple visual overview of the current state of your Kubernetes workloads as well as a roadmap for what can be improved. The dashboard provides a cluster wide overview as well as breaking out results by category, namespace, and workload.
|
||||
* 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="/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
|
||||
<img src="https://polaris.docs.fairwinds.com/img/architecture.svg" alt="Polaris Architecture" width="550"/>
|
||||
</p>
|
||||
|
||||
Our default standards in Polaris are rather high, so don’t be surprised if your score is lower than you might expect. A key goal for Polaris was to set a high standard and aim for great configuration by default. If the defaults we’ve included are too strict, it’s easy to adjust the configuration as part of the deployment configuration to better suit your workloads.
|
||||
## Documentation
|
||||
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
|
||||
|
||||
<!-- Begin boilerplate -->
|
||||
## Join the Fairwinds Open Source Community
|
||||
|
||||
## Admission Controller: Validating Webhook
|
||||
> [View installation instructions](docs/usage.md#webhook)
|
||||
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!
|
||||
|
||||
Polaris can be run as an admission controller that acts as a validating webhook. This accepts the same configuration as the dashboard, and can run the same validations. This webhook will reject any workloads that trigger a danger-level check. This is indicative of the greater goal of Polaris, not just to encourage better configuration through dashboard visibility, but to actually enforce it with this webhook. Polaris will not fix your workloads, only block them.
|
||||
<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>
|
||||
|
||||
The webhook only supports a fixed set of controller types. You can see the
|
||||
[list of supported controllers here](https://github.com/FairwindsOps/polaris/blob/master/cmd/polaris/webhook.go#L40)
|
||||
## Other Projects from Fairwinds
|
||||
|
||||
Unfortunately we have not found a way to display warnings as part of `kubectl` output unless we are rejecting a workload altogether. That 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.
|
||||
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
|
||||
|
||||
## CLI
|
||||
> [View installation instructions](docs/usage.md#cli)
|
||||
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.
|
||||
|
||||
Polaris can also be used on the command line, either to audit local files or a running cluster.
|
||||
This is particularly helpful for running Polaris against your infrastructure-as-code as part of a
|
||||
CI/CD pipeline. Use the available [command line flags](docs/usage.md#running-with-ci-cd)
|
||||
to cause CI/CD to fail if your Polaris score drops below a certain threshold, or if any danger-level issues arise.
|
||||
|
||||
# Installation and Usage
|
||||
See the [Usage Guide](/docs/usage.md) for details on different methods for installing and using Polaris.
|
||||
|
||||
# Contributing
|
||||
PRs welcome! Check out the [Contributing Guidelines](CONTRIBUTING.md),
|
||||
[Code of Conduct](CODE_OF_CONDUCT.md), and [Roadmap](ROADMAP.md) for more information.
|
||||
|
||||
# Further Information
|
||||
A history of changes to this project can be viewed in the [Changelog](CHANGELOG.md)
|
||||
|
||||
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)
|
||||
|
||||
# License
|
||||
Apache License 2.0
|
||||
<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>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Polaris Roadmap
|
||||
We plan to continue expanding the list of checks available to Polaris users,
|
||||
as well as the project's overall functionality.
|
||||
|
||||
If you have ideas for a new check, or for new Polaris features,
|
||||
you can submit a pull request for this file, or open an issue in GitHub.
|
||||
|
||||
## Check non-controllers
|
||||
We would like to implement checks for non-controller types, such as:
|
||||
* Ingresses
|
||||
* Services
|
||||
* RBAC roles/bindings
|
||||
|
||||
## Expand list of checks
|
||||
We'd like to increase our library of checks. Not all checks need to be enabled by default,
|
||||
so anything that would be useful across different organizations is worth checking in.
|
||||
|
||||
## Support more controllers in the validating webhook
|
||||
Currently the webhook only checks for a fixed set of controllers.
|
||||
|
||||
While we can't listen for _all_ possible controller types, we can block Pods that violate policy.
|
||||
We've chosen not to do this for now, because it could prevent existing controllers from scaling.
|
||||
|
||||
However, there may be a way to check for the owner's age, in order to determine if it's a new
|
||||
controller or a pre-existing controller.
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: CPU limits are set
|
||||
failureMessage: CPU limits should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: CPU requests are set
|
||||
failureMessage: CPU requests should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -14,12 +14,13 @@ schema:
|
||||
properties:
|
||||
add:
|
||||
type: array
|
||||
not:
|
||||
contains:
|
||||
const: ALL
|
||||
not:
|
||||
contains:
|
||||
const: SYS_ADMIN
|
||||
not:
|
||||
contains:
|
||||
const: NET_ADMIN
|
||||
allOf:
|
||||
- not:
|
||||
contains:
|
||||
pattern: '^(?i)ALL$'
|
||||
- not:
|
||||
contains:
|
||||
pattern: '^(?i)SYS_ADMIN$'
|
||||
- not:
|
||||
contains:
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
|
||||
@@ -8,19 +8,14 @@ controllers:
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- Object
|
||||
required:
|
||||
- spec
|
||||
properties:
|
||||
Object:
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
- replicas
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- replicas
|
||||
properties:
|
||||
replicas:
|
||||
type: integer
|
||||
minimum: 2
|
||||
replicas:
|
||||
type: integer
|
||||
minimum: 2
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Host network is not configured
|
||||
failureMessage: Host network should not be configured
|
||||
category: Networking
|
||||
category: Security
|
||||
target: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Host port is not configured
|
||||
failureMessage: Host port should not be configured
|
||||
category: Networking
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -5,27 +5,52 @@ 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:
|
||||
add:
|
||||
enum:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FSETID
|
||||
- FOWNER
|
||||
- MKNOD
|
||||
- NET_RAW
|
||||
- SETGID
|
||||
- SETUID
|
||||
- SETFCAP
|
||||
- SETPCAP
|
||||
- NET_BIND_SERVICE
|
||||
- SYS_CHROOT
|
||||
- KILL
|
||||
- AUDIT_WRITE
|
||||
|
||||
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$'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Liveness probe is configured
|
||||
failureMessage: Liveness probe should be configured
|
||||
category: Health Checks
|
||||
category: Reliability
|
||||
controllers:
|
||||
exclude:
|
||||
- Job
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Memory limits are set
|
||||
failureMessage: Memory limits should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Memory requests are set
|
||||
failureMessage: Memory requests should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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 }}"
|
||||
@@ -0,0 +1,39 @@
|
||||
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 }}
|
||||
@@ -2,15 +2,42 @@ successMessage: Filesystem is read only
|
||||
failureMessage: Filesystem should be read only
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
required:
|
||||
- readOnlyRootFilesystem
|
||||
definitions:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- readOnlyRootFilesystem
|
||||
properties:
|
||||
readOnlyRootFilesystem:
|
||||
const: true
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
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"
|
||||
@@ -0,0 +1,22 @@
|
||||
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%'
|
||||
@@ -2,12 +2,42 @@ successMessage: Privilege escalation not allowed
|
||||
failureMessage: Privilege escalation should not be allowed
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
definitions:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- allowPrivilegeEscalation
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
const: false
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
not:
|
||||
const: true
|
||||
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"
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Image pull policy is "Always"
|
||||
failureMessage: Image pull policy should be "Always"
|
||||
category: Images
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Readiness probe is configured
|
||||
failureMessage: Readiness probe should be configured
|
||||
category: Health Checks
|
||||
category: Reliability
|
||||
controllers:
|
||||
exclude:
|
||||
- Job
|
||||
|
||||
@@ -2,12 +2,23 @@ successMessage: Not running as privileged
|
||||
failureMessage: Should not be running as privileged
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
definitions:
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
properties:
|
||||
privileged:
|
||||
not:
|
||||
const: true
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Image tag is specified
|
||||
failureMessage: Image tag should be specified
|
||||
category: Images
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -32,22 +32,30 @@ import (
|
||||
)
|
||||
|
||||
var setExitCode bool
|
||||
var onlyShowFailedTests bool
|
||||
var minScore int
|
||||
var auditOutputURL string
|
||||
var auditOutputFile string
|
||||
var auditOutputFormat string
|
||||
var resourceToAudit string
|
||||
var useColor bool
|
||||
var helmChart string
|
||||
var helmValues 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, or score.")
|
||||
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().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")
|
||||
}
|
||||
|
||||
var auditCmd = &cobra.Command{
|
||||
@@ -58,8 +66,28 @@ var auditCmd = &cobra.Command{
|
||||
if displayName != "" {
|
||||
config.DisplayName = displayName
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat)
|
||||
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)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -73,28 +101,55 @@ var auditCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
// 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()
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
auditData, err := validator.RunAudit(ctx, c, k)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
if err != nil {
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
}
|
||||
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" {
|
||||
jsonBytes, err := json.Marshal(auditData)
|
||||
var jsonBytes []byte
|
||||
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, "", " ")
|
||||
}
|
||||
@@ -148,5 +203,4 @@ func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, wor
|
||||
}
|
||||
}
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
@@ -27,10 +27,12 @@ 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.")
|
||||
@@ -59,6 +61,6 @@ var dashboardCmd = &cobra.Command{
|
||||
http.Handle("/", router)
|
||||
|
||||
logrus.Infof("Starting Polaris dashboard server on port %d", serverPort)
|
||||
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil))
|
||||
logrus.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", listeningAddress, serverPort), nil))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var configPath string
|
||||
var disallowExemptions bool
|
||||
var disallowExemptions, disallowConfigExemptions, disallowAnnotationExemptions bool
|
||||
var logLevel string
|
||||
var auditPath string
|
||||
var displayName string
|
||||
@@ -37,7 +37,9 @@ var (
|
||||
func init() {
|
||||
// Flags
|
||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Location of Polaris configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any exemptions from configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any configured exemption.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowConfigExemptions, "disallow-config-exemptions", "", false, "Disallow exemptions set within the configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowAnnotationExemptions, "disallow-annotation-exemptions", "", false, "Disallow any exemption defined as a controller annotation.")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level.")
|
||||
flag.Parse()
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
@@ -63,10 +65,9 @@ var rootCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if disallowExemptions {
|
||||
config.DisallowExemptions = true
|
||||
}
|
||||
|
||||
config.DisallowExemptions = disallowExemptions
|
||||
config.DisallowConfigExemptions = disallowConfigExemptions
|
||||
config.DisallowAnnotationExemptions = disallowAnnotationExemptions
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Error("You must specify a sub-command.")
|
||||
|
||||
@@ -24,7 +24,7 @@ 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/runtime/signals"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
)
|
||||
|
||||
var webhookPort int
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
coverage:
|
||||
range: 50...80
|
||||
|
Before Width: | Height: | Size: 267 KiB |
@@ -5,7 +5,7 @@ kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
# Source: polaris/templates/serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
@@ -15,7 +15,7 @@ metadata:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -31,9 +31,17 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-view
|
||||
@@ -49,7 +57,7 @@ subjects:
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -94,7 +102,7 @@ metadata:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
spec:
|
||||
replicas: 1
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
@@ -109,7 +117,9 @@ spec:
|
||||
- command:
|
||||
- polaris
|
||||
- dashboard
|
||||
image: 'quay.io/fairwinds/polaris:1.2'
|
||||
- --port
|
||||
- "8080"
|
||||
image: 'quay.io/fairwinds/polaris:5.0'
|
||||
imagePullPolicy: 'Always'
|
||||
name: dashboard
|
||||
ports:
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
# Source: polaris/templates/serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
@@ -14,31 +14,8 @@ metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.secret.yaml
|
||||
# The name of this secret is static as it is populated by the webhook pod.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
tls.crt: ''
|
||||
tls.key: ''
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -54,51 +31,17 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'secrets'
|
||||
- 'services'
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- 'watch'
|
||||
- apiGroups:
|
||||
- 'certificates.k8s.io'
|
||||
resources:
|
||||
- 'certificatesigningrequests'
|
||||
- 'certificatesigningrequests/approval'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'update'
|
||||
- 'create'
|
||||
- 'delete'
|
||||
- apiGroups:
|
||||
- 'certificates.k8s.io'
|
||||
resources:
|
||||
- 'signers'
|
||||
resourceNames:
|
||||
- 'kubernetes.io/legacy-unknown'
|
||||
verbs:
|
||||
- 'approve'
|
||||
- apiGroups:
|
||||
- 'admissionregistration.k8s.io'
|
||||
resources:
|
||||
- 'validatingwebhookconfigurations'
|
||||
verbs:
|
||||
- '*'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-view
|
||||
@@ -114,7 +57,7 @@ subjects:
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -129,57 +72,6 @@ subjects:
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
# required for current controller-runtime bootstrap method
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'secrets'
|
||||
- 'services'
|
||||
verbs:
|
||||
- '*'
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -209,7 +101,7 @@ metadata:
|
||||
app: polaris
|
||||
component: webhook
|
||||
spec:
|
||||
replicas: 1
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
@@ -225,7 +117,7 @@ spec:
|
||||
command:
|
||||
- polaris
|
||||
- webhook
|
||||
image: 'quay.io/fairwinds/polaris:1.2'
|
||||
image: 'quay.io/fairwinds/polaris:5.0'
|
||||
imagePullPolicy: 'Always'
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
@@ -276,93 +168,66 @@ spec:
|
||||
volumes:
|
||||
- name: secret
|
||||
secret:
|
||||
secretName: polaris-webhook
|
||||
secretName: polaris
|
||||
- name: cr-logs
|
||||
emptyDir: {}
|
||||
---
|
||||
# Source: polaris/templates/webhook.job.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
# Source: polaris/templates/webhook.cert.yaml
|
||||
apiVersion: cert-manager.io/v1alpha2
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: polaris-certificate-updater
|
||||
name: polaris-cert
|
||||
namespace: polaris
|
||||
labels:
|
||||
|
||||
app: polaris
|
||||
component: certificate-updater
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
app: polaris
|
||||
component: certificate-updater
|
||||
spec:
|
||||
containers:
|
||||
- name: webhook-certificate-generator
|
||||
image: 'newrelic/k8s-webhook-cert-manager:1.3.0'
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- ./generate_certificate.sh
|
||||
- --service
|
||||
- polaris-webhook
|
||||
- --namespace
|
||||
- polaris
|
||||
- --secret
|
||||
- polaris-webhook
|
||||
- --webhook
|
||||
- polaris-webhook
|
||||
- --webhook-kind
|
||||
- ValidatingWebhookConfiguration
|
||||
resources:
|
||||
limits:
|
||||
cpu: 150m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp/
|
||||
readOnly: false
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
serviceAccountName: polaris-certificates
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
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:
|
||||
caBundle: ""
|
||||
service:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
path: /validate
|
||||
port: 443
|
||||
failurePolicy: Ignore
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: polaris.fairwinds.com
|
||||
namespaceSelector:
|
||||
|
||||
matchExpressions:
|
||||
- key: control-plane
|
||||
operator: DoesNotExist
|
||||
objectSelector: {}
|
||||
objectSelector:
|
||||
|
||||
{}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
@@ -402,4 +267,4 @@ webhooks:
|
||||
- replicationcontrollers
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
module.exports = {
|
||||
title: "Fairwinds Polaris Documentation",
|
||||
description: "Documentation for Fairwinds Polaris - audit and enforce Kubernetes best practices for your workloads",
|
||||
themeConfig: {
|
||||
docsRepo: "FairwindsOps/polaris",
|
||||
sidebar: [
|
||||
{
|
||||
title: "Polaris",
|
||||
path: "/",
|
||||
sidebarDepth: 0,
|
||||
collapsable: false,
|
||||
children: [
|
||||
{
|
||||
title: "Changelog",
|
||||
path: "/changelog",
|
||||
},
|
||||
{
|
||||
title: "Code of Conduct",
|
||||
path: "/code-of-conduct",
|
||||
},
|
||||
{
|
||||
title: "Contributing",
|
||||
path: "/contributing",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Ways to Run Polaris",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/dashboard",
|
||||
"/admission-controller",
|
||||
"/infrastructure-as-code",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Usage",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/cli",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Customization",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/customization/configuration",
|
||||
"/customization/checks",
|
||||
"/customization/custom-checks",
|
||||
"/customization/exemptions",
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Checks",
|
||||
collapsable: false,
|
||||
sidebarDepth: 0,
|
||||
children: [
|
||||
"/checks/security",
|
||||
"/checks/efficiency",
|
||||
"/checks/reliability",
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// This file is generated from FairwindsOps/documentation-template
|
||||
// DO NOT EDIT MANUALLY
|
||||
|
||||
const fs = require('fs');
|
||||
const npath = require('path');
|
||||
|
||||
const CONFIG_FILE = npath.join(__dirname, 'config-extras.js');
|
||||
const BASE_DIR = npath.join(__dirname, '..');
|
||||
|
||||
const extras = require(CONFIG_FILE);
|
||||
if (!extras.title || !extras.description || !extras.themeConfig.docsRepo) {
|
||||
throw new Error("Please specify 'title', 'description', and 'themeConfig.docsRepo' in config-extras.js");
|
||||
}
|
||||
|
||||
const docFiles = fs.readdirSync(BASE_DIR)
|
||||
.filter(f => f !== "README.md")
|
||||
.filter(f => f !== ".vuepress")
|
||||
.filter(f => f !== "node_modules")
|
||||
.filter(f => npath.extname(f) === '.md' || npath.extname(f) === '');
|
||||
|
||||
const sidebar = [['/', 'Home']].concat(docFiles.map(f => {
|
||||
const ext = npath.extname(f);
|
||||
if (ext === '') {
|
||||
// this is a directory
|
||||
const title = f;
|
||||
const children = fs.readdirSync(npath.join(BASE_DIR, f)).map(subf => {
|
||||
return '/' + f + '/' + npath.basename(subf);
|
||||
});
|
||||
return {title, children};
|
||||
}
|
||||
const path = npath.basename(f);
|
||||
return path;
|
||||
}));
|
||||
|
||||
const baseConfig = {
|
||||
title: "",
|
||||
description: "",
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/favicon.png' }],
|
||||
['script', { src: '/scripts/modify.js' }],
|
||||
['script', { src: '/scripts/marketing.js' }],
|
||||
],
|
||||
themeConfig: {
|
||||
docsRepo: "",
|
||||
docsDir: 'docs',
|
||||
editLinks: true,
|
||||
editLinkText: "Help us improve this page",
|
||||
logo: '/img/fairwinds-logo.svg',
|
||||
heroText: "",
|
||||
sidebar,
|
||||
nav: [
|
||||
{text: 'View on GitHub', link: 'https://github.com/' + extras.themeConfig.docsRepo},
|
||||
],
|
||||
},
|
||||
plugins: {
|
||||
'vuepress-plugin-clean-urls': {
|
||||
normalSuffix: '/',
|
||||
notFoundPath: '/404.html',
|
||||
},
|
||||
'check-md': {},
|
||||
},
|
||||
}
|
||||
|
||||
let config = JSON.parse(JSON.stringify(baseConfig))
|
||||
if (!fs.existsSync(CONFIG_FILE)) {
|
||||
throw new Error("Please add config-extras.js to specify your project details");
|
||||
}
|
||||
for (let key in extras) {
|
||||
if (!config[key]) config[key] = extras[key];
|
||||
else if (key === 'head') config[key] = config[key].concat(extras[key]);
|
||||
else Object.assign(config[key], extras[key]);
|
||||
}
|
||||
module.exports = config;
|
||||
|
After Width: | Height: | Size: 400 B |
@@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 3000 706.2" style="enable-background:new 0 0 3000 706.2;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#231039;}
|
||||
.st1{fill:#6D6D6D;}
|
||||
.st2{font-family:'CircularStd-Book';}
|
||||
.st3{font-size:88.0749px;}
|
||||
.st4{fill:#F36D21;}
|
||||
.st5{fill:none;stroke:#F36D21;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st6{fill:#898989;}
|
||||
.st7{fill:#BCBCBC;}
|
||||
.st8{fill:none;}
|
||||
.st9{fill:#DBDBDB;}
|
||||
.st10{enable-background:new ;}
|
||||
.st11{fill:#23183A;}
|
||||
.st12{fill:#B5B5B5;}
|
||||
.st13{fill:#9F234F;}
|
||||
.st14{fill:#F08117;}
|
||||
.st15{fill:#8ED0D9;}
|
||||
.st16{fill:#A3A3A3;}
|
||||
.st17{fill:#D1D1D1;}
|
||||
.st18{fill:#D1D1D1;stroke:#23183A;stroke-width:2.3711;stroke-miterlimit:10;}
|
||||
.st19{fill:none;stroke:#D1D1D1;stroke-width:6.6619;stroke-miterlimit:10;}
|
||||
.st20{fill:#23103A;}
|
||||
.st21{fill:#270D3C;}
|
||||
.st22{fill:#26163C;}
|
||||
.st23{fill:#F26722;}
|
||||
.st24{fill:#F78F1E;}
|
||||
.st25{fill:#F9AC78;}
|
||||
.st26{fill:#FF6700;}
|
||||
.st27{fill:url(#SVGID_1_);}
|
||||
.st28{fill:url(#SVGID_2_);}
|
||||
.st29{fill:url(#SVGID_3_);}
|
||||
.st30{fill:url(#SVGID_4_);}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M317.7,177.6h-149v-49.3h174.4v23.9C343.1,166.3,331.7,177.6,317.7,177.6z"/>
|
||||
<path class="st0" d="M254.3,247.2h-85.6v-49.3h111v23.9C279.7,235.9,268.3,247.2,254.3,247.2z"/>
|
||||
<path class="st0" d="M192.7,316.8h-24v-49.3H218v23.9C218,305.5,206.7,316.8,192.7,316.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M398.5,287.3V136.7h93.7v24H424V203h61.2v23.6H424v60.8H398.5z"/>
|
||||
<path class="st0" d="M532.1,228l26.6-4c6-0.8,7.6-3.8,7.6-7.4c0-8.7-5.9-15.7-19.5-15.7c-13,0-20.2,8.3-21.2,18.7l-22.5-5.1
|
||||
c1.9-17.8,18.1-33.8,43.6-33.8c31.9,0,44,18.1,44,38.7v51.4c0,9.3,1.1,15.5,1.3,16.6h-22.9c-0.2-0.6-1.1-4.9-1.1-13.2
|
||||
c-4.9,7.9-15.1,16.4-31.9,16.4c-21.7,0-35.1-14.9-35.1-31.2C500.9,240.8,514.5,230.6,532.1,228z M566.3,244.4v-4.7l-27,4
|
||||
c-7.6,1.3-13.8,5.5-13.8,14c0,7,5.3,13.4,15.1,13.4C554.4,271.2,566.3,264.6,566.3,244.4z"/>
|
||||
<path class="st0" d="M632.4,131.4c8.9,0,16.1,7.2,16.1,16.1s-7.2,15.9-16.1,15.9c-8.7,0-15.9-7-15.9-15.9S623.7,131.4,632.4,131.4
|
||||
z M620.3,287.3V183.9h24.4v103.5H620.3z"/>
|
||||
<path class="st0" d="M737.2,208.3c-2.8-0.4-5.5-0.6-8.1-0.6c-19.1,0-27.8,11-27.8,30.4v49.3h-24.6V183.9h24v16.6
|
||||
c4.9-11.3,16.4-17.8,30-17.8c3,0,5.5,0.4,6.6,0.6V208.3z"/>
|
||||
<path class="st0" d="M844.3,183.9l24.4,71.6l20.6-71.6h25.1l-33.1,103.5h-24.6L830.9,213l-25.1,74.4h-25.3L747,183.9h26.3l21,71.6
|
||||
l24.4-71.6H844.3z"/>
|
||||
<path class="st0" d="M945.2,131.4c8.9,0,16.1,7.2,16.1,16.1s-7.2,15.9-16.1,15.9c-8.7,0-15.9-7-15.9-15.9S936.5,131.4,945.2,131.4
|
||||
z M933.1,287.3V183.9h24.4v103.5H933.1z"/>
|
||||
<path class="st0" d="M1014.1,287.3h-24.6V183.9h24v13.8c6.8-11.9,19.1-16.8,30.6-16.8c25.3,0,37.4,18.3,37.4,41v65.4h-24.6v-61.2
|
||||
c0-12.8-5.7-22.9-21.2-22.9c-14,0-21.5,10.8-21.5,24.4V287.3z"/>
|
||||
<path class="st0" d="M1207,268.4c0,9.3,0.9,17.2,1.1,18.9h-23.6c-0.4-2.3-1.1-9.1-1.1-13.4c-4.9,8.7-15.7,15.9-30.4,15.9
|
||||
c-29.7,0-49.7-23.4-49.7-54.6c0-29.8,20.2-54.2,49.3-54.2c18.1,0,27.2,8.3,30.4,14.9v-62.5h24V268.4z M1155.8,268.2
|
||||
c16.1,0,27.4-13.4,27.4-33.1s-11-32.1-27.2-32.1c-16.1,0-28,12.5-28,32.3C1128,255,1139,268.2,1155.8,268.2z"/>
|
||||
<path class="st0" d="M1251.6,253.3c0.9,9.3,7.9,17.6,21,17.6c10.2,0,15.5-5.5,15.5-11.9c0-5.5-3.8-9.8-12.1-11.5l-15.3-3.4
|
||||
c-18.3-4-27.8-15.9-27.8-30.2c0-18.1,16.6-33.4,38-33.4c28.9,0,38.2,18.7,39.9,28.9l-20.8,5.9c-0.9-5.9-5.3-15.5-19.1-15.5
|
||||
c-8.7,0-14.9,5.5-14.9,11.9c0,5.5,4,9.6,10.4,10.8l15.7,3.2c19.5,4.2,29.7,16.2,29.7,31.4c0,15.7-12.7,33.1-39.1,33.1
|
||||
c-30,0-41.4-19.5-42.7-31.2L1251.6,253.3z"/>
|
||||
<path class="st0" d="M1391.2,287.3V136.7h25.9v150.7H1391.2z"/>
|
||||
<path class="st0" d="M1475.8,287.3h-24.6V183.9h24v13.8c6.8-11.9,19.1-16.8,30.6-16.8c25.3,0,37.4,18.3,37.4,41v65.4h-24.6v-61.2
|
||||
c0-12.8-5.7-22.9-21.2-22.9c-14,0-21.5,10.8-21.5,24.4V287.3z"/>
|
||||
<path class="st0" d="M1585.4,253.3c0.9,9.3,7.9,17.6,21,17.6c10.2,0,15.5-5.5,15.5-11.9c0-5.5-3.8-9.8-12.1-11.5l-15.3-3.4
|
||||
c-18.3-4-27.8-15.9-27.8-30.2c0-18.1,16.6-33.4,38-33.4c28.9,0,38.2,18.7,39.9,28.9l-20.8,5.9c-0.9-5.9-5.3-15.5-19.1-15.5
|
||||
c-8.7,0-14.9,5.5-14.9,11.9c0,5.5,4,9.6,10.4,10.8l15.7,3.2c19.5,4.2,29.7,16.2,29.7,31.4c0,15.7-12.7,33.1-39.1,33.1
|
||||
c-30,0-41.4-19.5-42.7-31.2L1585.4,253.3z"/>
|
||||
<path class="st0" d="M1682.3,131.4c8.9,0,16.1,7.2,16.1,16.1s-7.2,15.9-16.1,15.9c-8.7,0-15.9-7-15.9-15.9
|
||||
S1673.6,131.4,1682.3,131.4z M1670.2,287.3V183.9h24.4v103.5H1670.2z"/>
|
||||
<path class="st0" d="M1741.8,287.1c1.7,12.8,11.7,22.3,25.9,22.3c19.5,0,29.3-10,29.3-31v-10.8c-4.5,8.1-14.9,15.1-30,15.1
|
||||
c-27.6,0-47.8-21.2-47.8-50.4c0-27.6,19.3-50.4,47.8-50.4c16.1,0,26.1,6.6,30.6,15.1v-13.2h23.8v93.7c0,27.6-14.2,53.3-53.1,53.3
|
||||
c-27.4,0-46.5-17-49.3-37.8L1741.8,287.1z M1797.7,232.3c0-17.6-11.3-29.1-26.6-29.1c-15.7,0-27,11.5-27,29.1
|
||||
c0,18.1,10.8,29.3,27,29.3C1786.9,261.6,1797.7,250.1,1797.7,232.3z"/>
|
||||
<path class="st0" d="M1876.3,287.3h-24.6V133.5h24.6v60.3c7-9.1,18.7-13,29.5-13c25.5,0,37.8,18.3,37.8,41v65.4H1919v-61.2
|
||||
c0-12.8-5.7-22.9-21.2-22.9c-13.6,0-21,10.2-21.5,23.4V287.3z"/>
|
||||
<path class="st0" d="M2004.9,183.9h21.5v21.9h-21.5v48c0,9.1,4,13,13.2,13c3.4,0,7.4-0.6,8.5-0.8v20.4c-1.5,0.6-6.2,2.3-15.1,2.3
|
||||
c-19.1,0-31-11.5-31-30.8v-52.1h-19.1v-21.9h5.3c11,0,15.9-7,15.9-16.1v-15.5h22.3V183.9z"/>
|
||||
<path class="st0" d="M2065.7,253.3c0.9,9.3,7.9,17.6,21,17.6c10.2,0,15.5-5.5,15.5-11.9c0-5.5-3.8-9.8-12.1-11.5l-15.3-3.4
|
||||
c-18.3-4-27.8-15.9-27.8-30.2c0-18.1,16.6-33.4,38-33.4c28.9,0,38.2,18.7,39.9,28.9l-20.8,5.9c-0.9-5.9-5.3-15.5-19.1-15.5
|
||||
c-8.7,0-14.9,5.5-14.9,11.9c0,5.5,4,9.6,10.4,10.8l15.7,3.2c19.5,4.2,29.7,16.2,29.7,31.4c0,15.7-12.7,33.1-39.1,33.1
|
||||
c-30,0-41.4-19.5-42.7-31.2L2065.7,253.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 2512.0906 491.5612)" class="st1 st2 st3">more</text>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 2088.605 491.561)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 2439.4316 491.561)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 1567.2388 491.561)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 1250.0308 491.5612)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st5" d="M524.4,621.6c0,0,476.7-73.4,685.5-33.6"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M1453,505.9c-0.8-0.1-1.6,0.3-2,1V503h-1.2v9.2h1.2v-0.9c0.4,0.7,1.2,1.1,2,1c1.8,0,2.8-1.4,2.8-3.2
|
||||
C1455.8,507.2,1454.9,505.9,1453,505.9z M1452.8,511.3c-1,0-1.8-0.8-1.8-2.2c-0.1-1,0.6-2,1.6-2.1c0.1,0,0.1,0,0.2,0
|
||||
c1.1,0,1.8,0.8,1.8,2.1C1454.6,510.4,1453.9,511.3,1452.8,511.3z"/>
|
||||
<polygon class="st1" points="1459.4,510.3 1457.5,506.1 1456.2,506.1 1458.8,511.5 1457.3,514.7 1458.6,514.7 1462.6,506.1
|
||||
1461.2,506.1 "/>
|
||||
<path class="st1" d="M1466.4,512.6h1.1c0.5,0,0.9-0.4,0.9-0.9l0,0v-3.2h1.5c0.7,0,1.2-0.6,1.2-1.3v-0.6h-2.8v-2.2h3
|
||||
c0.6,0,1.1-0.5,1.1-1.1v-0.7h-6.2L1466.4,512.6z"/>
|
||||
<path class="st1" d="M1476.2,505.1c-0.7,0-1.4,0.2-2,0.7c-0.5,0.5-0.8,1.1-0.9,1.8h1.8c0-0.2,0.1-0.5,0.3-0.6
|
||||
c0.2-0.2,0.5-0.3,0.8-0.2c0.3,0,0.6,0.1,0.8,0.2c0.2,0.2,0.3,0.5,0.2,0.8v0.2l-1.9,0.3c-0.3,0-0.6,0.1-0.9,0.2
|
||||
c-0.3,0.1-0.5,0.2-0.7,0.4c-0.2,0.1-0.4,0.3-0.5,0.6c-0.1,0.3-0.2,0.6-0.2,0.9c0,0.6,0.2,1.1,0.6,1.5c0.4,0.4,1,0.6,1.6,0.6
|
||||
c0.9,0.1,1.7-0.4,2.1-1.1v0.2c0.1,0.5,0.4,0.8,0.9,0.8h0.8v-4.7c0.1-0.7-0.2-1.5-0.8-2C1477.7,505.3,1476.9,505.1,1476.2,505.1z
|
||||
M1477.2,509.6c0,0.3-0.1,0.6-0.2,0.8c-0.1,0.2-0.3,0.4-0.5,0.5c-0.2,0.1-0.4,0.2-0.7,0.2c-0.2,0-0.4,0-0.6-0.2
|
||||
c-0.1-0.1-0.2-0.3-0.2-0.5s0-0.4,0.1-0.5c0.2-0.1,0.4-0.2,0.6-0.2l1.5-0.3V509.6L1477.2,509.6z"/>
|
||||
<path class="st1" d="M1481.7,502.1c-0.3,0-0.6,0.1-0.8,0.3s-0.4,0.5-0.4,0.8s0.1,0.6,0.3,0.8c0.2,0.2,0.5,0.3,0.8,0.3
|
||||
s0.6-0.1,0.8-0.3s0.4-0.5,0.4-0.8s-0.1-0.6-0.4-0.8C1482.3,502.2,1482,502.1,1481.7,502.1z"/>
|
||||
<path class="st1" d="M1480.8,512.6h0.9c0.5,0,0.9-0.4,0.9-0.9l0,0v-6.4h-1.9v7.3H1480.8z"/>
|
||||
<path class="st1" d="M1486.4,506.4l-0.2-1.1h-1.6v7.3h1c0.5,0,0.9-0.4,0.9-0.9l0,0v-2.8c0-0.5,0.2-1,0.6-1.3
|
||||
c0.4-0.3,0.9-0.5,1.4-0.5v-1.8c-0.5-0.1-1,0.1-1.4,0.3C1486.8,505.7,1486.6,506,1486.4,506.4z"/>
|
||||
<path class="st1" d="M1497.3,509.8l-0.1,0.8l-0.1-0.8l-1.2-4.3h-1.7l-1.2,4.3l-0.1,0.8l-0.1-0.8l-1.1-4.3h-2l2.2,7.3h1.2
|
||||
c0.4,0,0.8-0.3,0.9-0.7l1.1-3.6l1.1,4.2h1.3c0.4,0,0.8-0.3,0.9-0.7l2-6.6h-2L1497.3,509.8z"/>
|
||||
<path class="st1" d="M1502.4,502.1c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.4,0.5-0.4,0.8s0.1,0.6,0.4,0.8c0.2,0.2,0.5,0.3,0.8,0.3
|
||||
s0.6-0.1,0.8-0.3s0.4-0.5,0.4-0.8s-0.1-0.6-0.4-0.8C1503,502.2,1502.7,502.1,1502.4,502.1z"/>
|
||||
<path class="st1" d="M1501.5,512.6h0.9c0.5,0,0.9-0.4,0.9-0.9l0,0v-6.4h-1.9v7.3H1501.5z"/>
|
||||
<path class="st1" d="M1509.1,505.1c-0.4,0-0.8,0.1-1.2,0.3c-0.3,0.2-0.6,0.5-0.8,0.8l-0.1-1h-1.7v7.4h1c0.5,0,1-0.5,0.9-1v-2.8
|
||||
c0-0.5,0.1-0.9,0.4-1.3c0.2-0.3,0.5-0.5,0.9-0.4c0.3,0,0.6,0.1,0.8,0.3c0.2,0.3,0.3,0.6,0.2,1v4.2h0.9c0.6,0,1-0.4,1-1v-3.7
|
||||
c0-0.7-0.2-1.4-0.6-2C1510.4,505.3,1509.7,505.1,1509.1,505.1z"/>
|
||||
<path class="st1" d="M1518.3,506.1c-0.2-0.3-0.5-0.5-0.9-0.7c-0.4-0.2-0.9-0.2-1.3-0.2c-0.9,0-1.8,0.4-2.4,1.1
|
||||
c-0.7,0.7-1,1.7-1,2.7c0,0.7,0.1,1.3,0.4,1.9c0.3,0.6,0.7,1,1.3,1.4c0.5,0.3,1.1,0.5,1.7,0.5c0.9,0.1,1.8-0.4,2.3-1.1v0.2
|
||||
c0.1,0.4,0.5,0.7,1,0.7h0.8v-10.5h-1.9V506.1z M1518.3,509c0,0.5-0.2,1.1-0.6,1.5c-0.3,0.4-0.8,0.6-1.3,0.6s-1-0.2-1.3-0.6
|
||||
c-0.7-0.9-0.7-2.1,0-2.9c0.3-0.4,0.8-0.6,1.3-0.6c0.3,0,0.6,0.1,0.9,0.2c0.3,0.2,0.5,0.4,0.6,0.7c0.2,0.3,0.3,0.6,0.3,1v0.1
|
||||
H1518.3z"/>
|
||||
<path class="st1" d="M1526.8,509c-0.5-0.4-1.1-0.7-1.8-0.8c-0.3-0.1-0.7-0.2-1-0.3c-0.2-0.1-0.3-0.3-0.2-0.5
|
||||
c0-0.2,0.1-0.4,0.2-0.5c0.1-0.2,0.3-0.2,0.6-0.2c0.4-0.1,0.8,0.2,0.8,0.6c0,0.1,0,0.1,0,0.2h1.8c0-0.6-0.2-1.3-0.8-1.7
|
||||
c-0.5-0.4-1.2-0.6-1.9-0.6c-0.7-0.1-1.5,0.2-2,0.7c-0.5,0.4-0.7,1-0.7,1.7c0,0.5,0.2,1,0.6,1.4c0.5,0.4,1.1,0.6,1.7,0.7
|
||||
c0.4,0.1,0.7,0.2,1,0.3c0.2,0.1,0.3,0.3,0.2,0.6c0,0.2-0.1,0.4-0.2,0.5c-0.2,0.2-0.4,0.2-0.7,0.2s-0.5-0.1-0.8-0.2l-0.1-0.2
|
||||
c-0.2-0.3-0.5-0.5-0.8-0.5h-1.2c0,0.7,0.4,1.4,1.1,1.8c0.6,0.4,1.3,0.6,2,0.6c0.4,0,0.7,0,1.1-0.2c0.3-0.1,0.6-0.2,0.9-0.4
|
||||
c0.3-0.2,0.5-0.4,0.6-0.7c0.1-0.3,0.2-0.7,0.2-1.1C1527.3,509.9,1527.1,509.4,1526.8,509z"/>
|
||||
</g>
|
||||
<path class="st1" d="M1334.1,495.3h-9.5v-48.8h9.3v7c3.6-6.1,9.3-8.4,15-8.4c11.7,0,17.5,8.4,17.5,19.3v31h-9.5v-29.3
|
||||
c0-6.8-2.8-12.3-11.4-12.3c-7.5,0-11.4,6-11.4,13.4V495.3z"/>
|
||||
<path class="st1" d="M1460.5,494.5h-9.6l-19.8-48.8h10.8l13.9,37.6l14-37.6h10.2L1460.5,494.5z"/>
|
||||
<path class="st1" d="M1500.3,466.6l13.2-1.9c2.9-0.4,3.8-1.9,3.8-3.7c0-4.8-3.1-8.7-10.6-8.7c-6.7,0-10.5,4.2-11.1,9.8l-9.1-2.1
|
||||
c1-9.3,9.4-15.8,20-15.8c14.6,0,20.3,8.3,20.3,17.9v24.5c0,4.3,0.4,6.8,0.6,8h-9.3c-0.2-1.2-0.5-3-0.5-6.6c-2.1,3.5-7,8.1-15.7,8.1
|
||||
c-9.9,0-16.3-6.9-16.3-14.6C1485.5,472.7,1491.9,467.9,1500.3,466.6z M1517.3,473.6v-2.2l-14.9,2.2c-4.1,0.6-7.1,2.9-7.1,7.3
|
||||
c0,3.7,3,7.1,7.9,7.1C1511,488.1,1517.3,484.3,1517.3,473.6z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M1977.8,504.3c-0.9,0-1.5,0.4-1.7,0.9v-3.4h-1v7.9h1v-0.8c0.3,0.6,0.9,0.9,1.7,0.9c1.6,0,2.4-1.2,2.4-2.8
|
||||
C1980.2,505.5,1979.4,504.3,1977.8,504.3z M1977.6,508.9c-0.9,0-1.6-0.7-1.6-1.9c0-1.1,0.7-1.8,1.6-1.8c1,0,1.6,0.7,1.6,1.8
|
||||
C1979.2,508.1,1978.6,508.9,1977.6,508.9z"/>
|
||||
<polygon class="st1" points="1983.4,508 1981.7,504.4 1980.5,504.4 1982.8,509.1 1981.5,511.9 1982.6,511.9 1986,504.4
|
||||
1984.9,504.4 "/>
|
||||
<path class="st1" d="M1989.4,510h0.9c0.4,0,0.8-0.3,0.8-0.8v-2.8h1.3c0.6,0,1.1-0.5,1.1-1.1v-0.5h-2.4V503h2.6c0.5,0,1-0.4,1-1
|
||||
v-0.6h-5.3V510z"/>
|
||||
<path class="st1" d="M1997.7,503.6c-0.7,0-1.3,0.2-1.7,0.6c-0.5,0.4-0.7,0.9-0.7,1.6h1.6c0-0.2,0.1-0.4,0.3-0.5s0.4-0.2,0.7-0.2
|
||||
s0.5,0,0.7,0.2c0.1,0.2,0.2,0.3,0.2,0.7v0.2l-1.6,0.3c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.4,0.2-0.6,0.3c-0.2,0.1-0.3,0.3-0.4,0.5
|
||||
c-0.1,0.2-0.1,0.4-0.1,0.7c0,0.6,0.2,1,0.5,1.3c0.4,0.3,0.8,0.5,1.4,0.5c0.8,0,1.4-0.3,1.8-1v0.2c0,0.4,0.4,0.7,0.8,0.7h0.7v-4.1
|
||||
c0-0.7-0.2-1.3-0.7-1.7S1998.5,503.6,1997.7,503.6z M1998.5,507.4c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.3-0.4,0.4
|
||||
c-0.2,0.1-0.3,0.1-0.6,0.1s-0.4,0-0.5-0.1c-0.1-0.1-0.2-0.2-0.2-0.4c0-0.2,0-0.3,0.1-0.4c0.1-0.1,0.3-0.2,0.5-0.2l1.3-0.3V507.4z"
|
||||
/>
|
||||
<path class="st1" d="M2002.6,501c-0.3,0-0.5,0.1-0.7,0.3c-0.2,0.2-0.3,0.4-0.3,0.7s0,0.5,0.3,0.7c0.2,0.2,0.4,0.3,0.7,0.3
|
||||
s0.5-0.1,0.7-0.3c0.2-0.2,0.3-0.4,0.3-0.7s-0.1-0.5-0.3-0.7C2003.1,501.1,2002.8,501,2002.6,501z"/>
|
||||
<path class="st1" d="M2001.8,510h0.8c0.4,0,0.8-0.3,0.8-0.8v-5.5h-1.6V510z"/>
|
||||
<path class="st1" d="M2006.5,504.7l-0.2-1h-1.4v6.2h0.9c0.4,0,0.8-0.3,0.8-0.8v-2.4c0-0.6,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.4,1.2-0.4
|
||||
v-1.6c-0.5,0-0.9,0.1-1.2,0.3C2006.9,504.1,2006.7,504.4,2006.5,504.7z"/>
|
||||
<path class="st1" d="M2015.9,507.5l-0.1,0.7l-0.1-0.7l-1-3.8h-1.4l-1,3.8l-0.1,0.7l-0.1-0.7l-1-3.8h-1.7l1.9,6.2h1
|
||||
c0.3,0,0.7-0.2,0.8-0.6l1-3.1l1.1,3.7h1.1c0.3,0,0.7-0.2,0.8-0.6l1.7-5.6h-1.7L2015.9,507.5z"/>
|
||||
<path class="st1" d="M2020.3,501c-0.3,0-0.5,0.1-0.7,0.3c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.4,0.3,0.7,0.3
|
||||
s0.5-0.1,0.7-0.3s0.3-0.4,0.3-0.7s-0.1-0.5-0.3-0.7C2020.8,501.1,2020.6,501,2020.3,501z"/>
|
||||
<path class="st1" d="M2019.5,510h0.8c0.4,0,0.8-0.3,0.8-0.8v-5.5h-1.6V510z"/>
|
||||
<path class="st1" d="M2026,503.6c-0.4,0-0.7,0.1-1,0.3c-0.3,0.2-0.5,0.4-0.7,0.7l-0.1-0.8h-1.4v6.2h0.8c0.4,0,0.8-0.3,0.8-0.8v-2.4
|
||||
c0-0.5,0.1-0.9,0.3-1.1s0.4-0.4,0.8-0.4c0.3,0,0.5,0.1,0.7,0.3c0.1,0.2,0.2,0.5,0.2,0.8v3.7h0.8c0.4,0,0.8-0.3,0.8-0.8v-3.2
|
||||
c0-0.7-0.2-1.3-0.5-1.7C2027.2,503.8,2026.7,503.6,2026,503.6z"/>
|
||||
<path class="st1" d="M2034,504.4c-0.2-0.2-0.4-0.4-0.7-0.6c-0.3-0.1-0.7-0.2-1.1-0.2c-0.8,0-1.5,0.3-2.1,0.9
|
||||
c-0.6,0.6-0.8,1.4-0.8,2.3c0,0.6,0.1,1.1,0.3,1.7c0.3,0.5,0.6,0.9,1,1.2s1,0.4,1.5,0.4c0.9,0,1.6-0.3,2-1l0,0.2
|
||||
c0.1,0.3,0.4,0.7,0.8,0.7h0.7v-9h-1.6V504.4z M2033.9,506.9c0,0.5-0.2,1-0.5,1.3c-0.3,0.3-0.7,0.5-1.1,0.5c-0.5,0-0.8-0.1-1.1-0.5
|
||||
c-0.3-0.3-0.4-0.7-0.4-1.3s0.1-1,0.4-1.3s0.7-0.5,1.1-0.5c0.3,0,0.5,0,0.7,0.2c0.2,0.1,0.4,0.3,0.6,0.6
|
||||
C2033.9,506.1,2033.9,506.4,2033.9,506.9L2033.9,506.9z"/>
|
||||
<path class="st1" d="M2041.3,506.9c-0.3-0.3-0.8-0.5-1.6-0.7c-0.4-0.1-0.7-0.2-0.9-0.3c-0.1-0.1-0.2-0.2-0.2-0.4
|
||||
c0-0.2,0-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.5-0.2c0.4,0,0.7,0.2,0.7,0.7h1.6c0-0.6-0.2-1.1-0.7-1.4c-0.4-0.3-1-0.5-1.6-0.5
|
||||
c-0.7,0-1.3,0.2-1.7,0.6c-0.4,0.4-0.6,0.9-0.6,1.4c0,0.5,0.1,0.9,0.5,1.2c0.3,0.3,0.8,0.5,1.5,0.6c0.4,0.1,0.7,0.2,0.9,0.3
|
||||
c0.1,0.1,0.2,0.3,0.2,0.5c0,0.2-0.1,0.3-0.2,0.4c-0.1,0.1-0.3,0.2-0.6,0.2s-0.5-0.1-0.7-0.2c0,0-0.1-0.1-0.1-0.1
|
||||
c-0.2-0.3-0.4-0.4-0.7-0.4h-1c0,0.6,0.3,1.1,0.9,1.5c0.4,0.3,1,0.5,1.7,0.5c0.3,0,0.6,0,0.9-0.1c0.3,0,0.5-0.2,0.7-0.3
|
||||
c0.2-0.1,0.4-0.3,0.5-0.6s0.2-0.6,0.2-0.9C2041.8,507.6,2041.6,507.2,2041.3,506.9z"/>
|
||||
<g>
|
||||
<path class="st1" d="M2011.4,483.3c0.7,4.7,5.5,12.1,16.9,12.1c10.3,0,15.2-7,15.2-12.9c0-6.1-4-10.7-11.5-12.3l-6.7-1.5
|
||||
c-3.2-0.7-4.8-2.5-4.8-5c0-2.8,2.8-5.5,6.9-5.5c6.6,0,8.3,4.8,8.6,6.9l7.2-2.7c-0.9-3.7-4.4-11.1-15.8-11.1
|
||||
c-8.1,0-14.8,6-14.8,13.1c0,5.9,3.9,10.3,10.4,11.7l7.1,1.6c3.5,0.8,5.4,2.8,5.4,5.4c0,2.9-2.5,5.5-7.1,5.5
|
||||
c-5.9,0-9.2-3.5-9.7-7.8L2011.4,483.3z M2007.1,452.6h-11.2l-18,18.4v-39.4h-8v62.5h8v-12.5l5.8-5.8l13.6,18.3h10.2l-18-24
|
||||
L2007.1,452.6z M1941.6,458.8c7.1,0,10.1,4.7,11.1,8.8l7.3-3.1c-1.7-6.3-7.4-13.2-18.5-13.2c-11.8,0-20.9,9.1-20.9,22
|
||||
c0,12.6,8.9,22.1,21,22.1c10.8,0,16.8-7,18.7-13l-7.2-3.1c-0.9,3.3-3.8,8.6-11.6,8.6c-6.7,0-12.7-5.1-12.7-14.6
|
||||
C1929,463.6,1935,458.8,1941.6,458.8 M1892.5,451.3c-12.4,0-21.2,9.4-21.2,22c0,12.7,8.8,22.1,21.2,22.1
|
||||
c12.4,0,21.2-9.4,21.2-22.1C1913.8,460.7,1905,451.3,1892.5,451.3 M1892.5,488.1c-6.9,0-13-5.2-13-14.9c0-9.6,6-14.8,13-14.8
|
||||
c6.9,0,13,5.2,13,14.8C1905.5,483,1899.4,488.1,1892.5,488.1 M1860.9,431.6h-8.1v62.5h8.1V431.6z M1829.1,436.8
|
||||
c0,3.2,2.6,5.8,5.8,5.8c3.3,0,5.9-2.6,5.9-5.8c0-3.3-2.6-5.9-5.9-5.9C1831.7,431,1829.1,433.6,1829.1,436.8 M1839,452.6h-8v41.5h8
|
||||
V452.6z M1809.3,488.5c0,2.5,0.3,4.7,0.4,5.6h7.9c-0.1-0.7-0.4-3.7-0.4-7.7v-54.8h-8v26.9c-1.4-3.4-5.4-7-12.7-7
|
||||
c-11.7,0-19.7,10-19.7,21.8c0,12.4,7.5,22,19.7,22c6.5,0,10.9-3.5,12.9-7.4V488.5L1809.3,488.5z M1785,473.2
|
||||
c0-8.5,4.8-14.6,12.3-14.6c7.4,0,11.9,5.9,11.9,14.4c0,8.6-4.6,15-12,15C1789.4,488.1,1785,481.7,1785,473.2 M1766.3,431.6h-8.1
|
||||
v62.5h8.1V431.6z M1696.7,494.1v-30h-29.3v7.6h21.2c-0.3,6.3-5.3,16-18.9,16c-11,0-21.3-8-21.3-24.3c0-16.2,10.7-24.1,21.7-24.1
|
||||
c9.5,0,16.7,5.2,19.4,14l7.7-3.3c-3.5-11.1-13.8-18.5-27.1-18.5c-15.1,0-30.3,11.1-30.3,31.9c0,20.7,14.3,32,29.7,32
|
||||
c10.3,0,16.9-5.1,19.6-9.9l0.8,8.6C1689.8,494.1,1696.7,494.1,1696.7,494.1z"/>
|
||||
<g>
|
||||
<path class="st1" d="M1740.6,509.4c-9.3,0-21.3-12.9-29.5-27.8c-4.9-8.8-8.2-17.9-9.5-25.4c-1.8-11.2,1.6-15.6,4.7-17.4
|
||||
c3.1-1.7,8.7-2.3,17.2,5.2c5.8,5,11.7,12.7,16.6,21.5c9.6,17.3,14.3,37.5,4.7,42.8C1743.6,509,1742.1,509.4,1740.6,509.4z
|
||||
M1710.7,444.6c-0.4,0-0.7,0.1-1,0.2c-1.3,0.7-2.2,4.2-1.3,10.3c1.1,6.8,4.2,15,8.7,23.2c10.3,18.6,21.5,25.6,24.4,24
|
||||
c2.9-1.6,2.9-14.9-7.4-33.4c-4.5-8.1-9.9-15.1-15.1-19.6C1715.4,445.9,1712.4,444.6,1710.7,444.6z"/>
|
||||
</g>
|
||||
<polygon class="st6" points="1725.6,495.2 1728.5,497.4 1730.7,494.5 1734,496 1735.4,492.7 1739,493.4 1739.6,489.9
|
||||
1743.2,489.6 1743,486 1746.4,485 1745.4,481.6 1748.4,479.7 1746.6,476.6 1749.1,474.1 1746.6,471.5 1748.4,468.5 1745.4,466.6
|
||||
1746.4,463.2 1743,462.1 1743.2,458.5 1739.6,458.3 1739,454.8 1735.4,455.4 1734,452.1 1730.7,453.6 1728.5,450.8 1725.6,453
|
||||
1722.8,450.8 1720.6,453.6 1717.3,452.1 1715.9,455.4 1712.3,454.8 1711.7,458.3 1708.1,458.5 1708.3,462.1 1704.9,463.2
|
||||
1705.9,466.6 1702.9,468.5 1704.7,471.5 1702.2,474.1 1704.7,476.6 1702.9,479.7 1705.9,481.6 1704.9,485 1708.3,486.1
|
||||
1708.1,489.6 1711.7,489.9 1712.3,493.4 1715.9,492.7 1717.3,496 1720.6,494.5 1722.8,497.4 "/>
|
||||
<g>
|
||||
<path class="st7" d="M1725.6,491.9c9.8,0,17.8-8,17.8-17.8c0-9.8-8-17.8-17.8-17.8c-9.8,0-17.8,8-17.8,17.8
|
||||
C1707.8,483.9,1715.8,491.9,1725.6,491.9"/>
|
||||
<g>
|
||||
<path class="st1" d="M1740.6,509.4c-9.6,0-21.7-13.7-29.5-27.8c-4.9-8.8-8.2-17.9-9.5-25.4c-1.8-11.2,1.6-15.6,4.7-17.4l3.3,6
|
||||
c-1.3,0.7-2.2,4.2-1.3,10.3c1.1,6.8,4.2,15,8.7,23.2c9.3,16.9,20.4,25.4,24.2,24.1l2.2,6.5
|
||||
C1742.6,509.2,1741.6,509.4,1740.6,509.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M2177.7,512.3v-64.9h8.8v7.2c2.5-4.7,8.1-8.3,15.6-8.3c13.9,0,21.4,10.7,21.4,24.3c0,13.9-8,24.6-21.7,24.6
|
||||
c-7.1,0-12.6-3.4-15.1-7.5v24.7L2177.7,512.3L2177.7,512.3z M2200.4,454.3c-8.3,0-13.9,7-13.9,16.3c0,9.6,5.5,16.4,13.9,16.4
|
||||
c8.4,0,13.8-6.9,13.8-16.4C2214.2,461.3,2208.9,454.3,2200.4,454.3z"/>
|
||||
<path class="st1" d="M2235.1,493.9v-70h9.1v70.1L2235.1,493.9L2235.1,493.9z"/>
|
||||
<path class="st1" d="M2276.1,495.3c-10.8,0-17.4-8.1-17.4-18.7v-29.3h9.1v27.9c0,6.4,2.9,12,10.5,12c7.3,0,11-4.9,11-11.9v-28h9.1
|
||||
v38c0,3.7,0.3,7,0.5,8.6h-8.7c-0.2-1.1-0.4-3.7-0.4-5.6C2287.3,493.1,2281.5,495.3,2276.1,495.3z"/>
|
||||
<path class="st1" d="M2326.2,447.4h10.1v8.2h-10.1v24.2c0,4.3,1.7,6.4,6.5,6.4c1.2,0,2.8-0.2,3.6-0.4v7.7c-0.8,0.3-3.1,0.9-6.2,0.9
|
||||
c-7.9,0-13-4.9-13-13.3v-25.5h-8.9v-8.2h2.5c5.1,0,7.1-3.1,7.1-7.1v-7.6h8.3V447.4z"/>
|
||||
<rect x="2353.9" y="456.8" class="st8" width="26.1" height="26.1"/>
|
||||
<g id="XMLID_3_">
|
||||
<g>
|
||||
<path class="st1" d="M2367.8,445.7c15.1,0,25.8,10.5,25.8,24.7c0,14.2-10.7,24.8-25.8,24.8c-15.1,0-25.8-10.5-25.8-24.8
|
||||
C2342,456.2,2352.7,445.7,2367.8,445.7z M2384.9,470.1c0-9.4-7.7-17.1-17.1-17.1s-17.1,7.7-17.1,17.1c0,9.4,7.7,17.1,17.1,17.1
|
||||
C2377.2,487.2,2384.9,479.6,2384.9,470.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M2320.6,504.5c-1,0-1.7,0.5-1.9,1v-3.8h-1.1v8.8h1.1v-0.9c0.4,0.6,1,1,1.9,1c1.7,0,2.7-1.4,2.7-3.1
|
||||
C2323.3,505.9,2322.4,504.5,2320.6,504.5z M2320.4,509.7c-1,0-1.7-0.8-1.7-2.1c0-1.3,0.7-2.1,1.7-2.1c1.1,0,1.7,0.8,1.7,2.1
|
||||
C2322.1,508.9,2321.4,509.7,2320.4,509.7z"/>
|
||||
<polygon class="st1" points="2326.8,508.7 2324.9,504.7 2323.6,504.7 2326.1,509.9 2324.7,513 2325.9,513 2329.7,504.7
|
||||
2328.5,504.7 "/>
|
||||
<path class="st1" d="M2333.4,510.9h1c0.5,0,0.9-0.4,0.9-0.9V507h1.5c0.7,0,1.2-0.6,1.2-1.2v-0.5h-2.7v-2.1h2.9
|
||||
c0.6,0,1.1-0.5,1.1-1.1v-0.7h-5.9V510.9z"/>
|
||||
<path class="st1" d="M2342.7,503.8c-0.8,0-1.5,0.2-1.9,0.7c-0.5,0.4-0.8,1-0.8,1.7h1.7c0-0.2,0.1-0.4,0.3-0.6s0.4-0.2,0.7-0.2
|
||||
c0.3,0,0.6,0,0.7,0.2c0.1,0.2,0.2,0.4,0.2,0.7v0.2l-1.8,0.3c-0.3,0-0.6,0.1-0.9,0.2c-0.2,0.1-0.5,0.2-0.7,0.4
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.6s-0.1,0.5-0.1,0.8c0,0.6,0.2,1.2,0.6,1.5c0.4,0.4,0.9,0.6,1.6,0.6c0.9,0,1.6-0.3,2-1.1v0.2
|
||||
c0,0.4,0.4,0.8,0.9,0.8h0.7v-4.6c0-0.8-0.2-1.5-0.7-1.9C2344.2,504,2343.6,503.8,2342.7,503.8z M2343.7,508c0,0.3-0.1,0.6-0.2,0.8
|
||||
c-0.1,0.2-0.3,0.3-0.5,0.4c-0.2,0.1-0.4,0.1-0.6,0.1c-0.2,0-0.4,0-0.6-0.1c-0.1-0.1-0.2-0.2-0.2-0.4c0-0.2,0-0.4,0.1-0.5
|
||||
c0.1-0.1,0.3-0.2,0.6-0.2l1.4-0.3V508z"/>
|
||||
<path class="st1" d="M2348.1,500.9c-0.3,0-0.5,0.1-0.7,0.3c-0.2,0.2-0.3,0.5-0.3,0.8c0,0.3,0,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.8,0.3
|
||||
c0.3,0,0.6-0.1,0.8-0.3c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.6-0.3-0.8C2348.7,501,2348.4,500.9,2348.1,500.9z"/>
|
||||
<path class="st1" d="M2347.2,510.9h0.9c0.5,0,0.9-0.4,0.9-0.9V504h-1.8V510.9z"/>
|
||||
<path class="st1" d="M2352.5,505l-0.2-1.1h-1.6v7h1c0.5,0,0.9-0.4,0.9-0.9v-2.7c0-0.6,0.2-1,0.5-1.3c0.3-0.3,0.8-0.4,1.4-0.4v-1.7
|
||||
c-0.5,0-1,0.1-1.3,0.3C2352.9,504.4,2352.7,504.7,2352.5,505z"/>
|
||||
<path class="st1" d="M2362.9,508.2l-0.1,0.7l-0.1-0.7l-1.2-4.2h-1.6l-1.2,4.2l-0.1,0.7l-0.1-0.7l-1.1-4.2h-1.9l2.1,6.9h1.1
|
||||
c0.4,0,0.7-0.2,0.9-0.6l1.1-3.5l1.3,4.1h1.2c0.4,0,0.8-0.2,0.9-0.6l1.9-6.3h-1.9L2362.9,508.2z"/>
|
||||
<path class="st1" d="M2367.9,500.9c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.6,0.3,0.7
|
||||
c0.2,0.2,0.5,0.3,0.8,0.3c0.3,0,0.5-0.1,0.8-0.3c0.2-0.2,0.3-0.4,0.3-0.7s-0.1-0.5-0.3-0.7C2368.4,501,2368.2,500.9,2367.9,500.9z
|
||||
"/>
|
||||
<path class="st1" d="M2367,510.9h0.9c0.5,0,0.9-0.4,0.9-0.9V504h-1.8V510.9z"/>
|
||||
<path class="st1" d="M2374.2,503.8c-0.5,0-0.8,0.1-1.2,0.3c-0.3,0.2-0.5,0.4-0.7,0.8l-0.1-0.9h-1.6v7h0.9c0.5,0,0.9-0.4,0.9-0.9
|
||||
v-2.7c0-0.6,0.1-1,0.3-1.3s0.5-0.4,0.9-0.4c0.3,0,0.6,0.1,0.7,0.3c0.1,0.2,0.2,0.5,0.2,0.9v4.1h0.9c0.5,0,0.9-0.4,0.9-0.9v-3.6
|
||||
c0-0.8-0.2-1.4-0.6-1.9C2375.5,504,2375,503.8,2374.2,503.8z"/>
|
||||
<path class="st1" d="M2383.1,504.7c-0.2-0.2-0.4-0.4-0.8-0.6c-0.4-0.1-0.8-0.2-1.3-0.2c-0.9,0-1.7,0.3-2.3,1
|
||||
c-0.6,0.7-0.9,1.6-0.9,2.6c0,0.7,0.1,1.3,0.4,1.8c0.3,0.6,0.7,1,1.2,1.3c0.5,0.3,1.1,0.5,1.7,0.5c1,0,1.7-0.3,2.2-1.1l0,0.2
|
||||
c0.1,0.4,0.4,0.7,0.9,0.7h0.8v-10h-1.8V504.7z M2383,507.5c0,0.6-0.2,1.1-0.5,1.4s-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.1-1.3-0.5
|
||||
c-0.3-0.3-0.5-0.8-0.5-1.4s0.1-1.1,0.5-1.4c0.3-0.3,0.8-0.5,1.3-0.5c0.3,0,0.6,0,0.8,0.2c0.2,0.1,0.4,0.3,0.6,0.6
|
||||
C2382.9,506.6,2383,507,2383,507.5L2383,507.5z"/>
|
||||
<path class="st1" d="M2391.2,507.5c-0.3-0.3-0.9-0.6-1.7-0.7c-0.5-0.1-0.8-0.2-1-0.3c-0.1-0.1-0.2-0.2-0.2-0.5
|
||||
c0-0.2,0-0.3,0.2-0.5c0.1-0.1,0.3-0.2,0.5-0.2c0.5,0,0.8,0.2,0.8,0.8h1.7c0-0.7-0.2-1.2-0.7-1.6c-0.4-0.4-1.1-0.6-1.8-0.6
|
||||
c-0.8,0-1.5,0.2-1.9,0.6c-0.4,0.4-0.7,1-0.7,1.6c0,0.6,0.1,1,0.5,1.4c0.3,0.3,0.9,0.5,1.7,0.7c0.4,0.1,0.8,0.2,1,0.3
|
||||
c0.1,0.1,0.2,0.3,0.2,0.5c0,0.2-0.1,0.4-0.2,0.5c-0.1,0.1-0.3,0.2-0.6,0.2c-0.3,0-0.5-0.1-0.7-0.2c0,0-0.1-0.1-0.1-0.1
|
||||
c-0.2-0.3-0.4-0.5-0.8-0.5h-1.1c0,0.7,0.3,1.3,1,1.7c0.4,0.4,1.1,0.6,1.9,0.6c0.4,0,0.7,0,1-0.1c0.3,0,0.6-0.2,0.8-0.4
|
||||
c0.2-0.1,0.4-0.4,0.6-0.7s0.2-0.6,0.2-1C2391.8,508.2,2391.6,507.8,2391.2,507.5z"/>
|
||||
</g>
|
||||
<path class="st9" d="M2379,461.3c-0.1,0.1-0.1,0.1-0.1,0.2c-0.1,0.5-0.4,0.9-0.6,1.4c-0.3,0.8,0.1,2.7-0.7,3.2
|
||||
c-1,0.6-2.1-1.3-3-2.1c-0.9-0.8-2.5-0.4-3.6-0.7c-0.9-0.2-1.8-0.9-2.8-0.6c-0.8,0.2-1.6,1.3-2.4,1.6c0-0.4,0-0.8-0.4-0.5
|
||||
c-0.9,0.7-1.6,1.5-2.5,2.1c-0.1,0.1-0.1-0.1-0.2,0c-0.1,0.2-1.2,0.1-1.2,0.4c0,1-0.1,2.4-1,2.8c-0.1,0-0.4-1.4-0.5-1.5
|
||||
c-0.1,0-0.9,1-1,0.8c-0.7-0.8-1.3-1.2-2.3-1.7c-0.5-0.3-1.8,0.6-2.2,0.2c-0.1-0.1-0.2-0.2-0.3-0.3c-0.3,1.2-0.5,2.5-0.5,3.7
|
||||
c0,7.9,6.4,14.3,14.3,14.3c7.9,0,14.3-6.4,14.3-14.3C2382.2,466.8,2381,463.7,2379,461.3z"/>
|
||||
<path class="st1" d="M2361.5,466.1c0,1-0.1,2.4-1,2.8c-0.1,0-0.4-1.4-0.5-1.5c-0.1,0-0.9,1-1,0.8c-0.7-0.8-1.3-1.2-2.3-1.7
|
||||
c-0.5-0.3-1.8,0.6-2.2,0.2c-0.1-0.1-0.2-0.2-0.3-0.3c1.7-6.1,7.2-10.5,13.8-10.5c4.5,0,8.5,2.1,11.1,5.3c0,0.1-0.1,0.1-0.1,0.2
|
||||
c-0.1,0.5-0.4,0.9-0.6,1.4c-0.3,0.8,0,2.7-0.7,3.2c-1,0.6-2.1-1.3-3-2.1c-0.9-0.8-2.5-0.4-3.6-0.7c-0.9-0.2-1.8-0.9-2.8-0.6
|
||||
c-0.8,0.2-1.6,1.3-2.4,1.6c0-0.4,0-0.8-0.4-0.5c-0.9,0.7-1.7,1.5-2.5,2.1c-0.1,0.1-0.1-0.1-0.2,0
|
||||
C2362.6,466,2361.5,465.9,2361.5,466.1z"/>
|
||||
<path class="st1" d="M2376.5,472.6c-1.1-0.2-2.1,0.1-2.9,0.8c-0.4-0.9-1.3-1.7-2.4-1.9c-1.8-0.4-3.6,0.8-3.9,2.6
|
||||
c-0.7,3.6,3.9,8.1,4.1,8.3c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0,0.2,0,0.4,0c0.2-0.1,6.3-2.4,7-6C2379.5,474.8,2378.4,473,2376.5,472.6z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<g class="st10">
|
||||
<path class="st11" d="M611.3,540V410.3h17.6v14.5c5-9.3,16.1-16.6,31.1-16.6c27.8,0,42.7,21.5,42.7,48.5
|
||||
c0,27.7-16.1,49.1-43.3,49.1c-14.3,0-25.1-6.8-30.2-15.1V540H611.3z M656.8,424.2c-16.6,0-27.7,13.9-27.7,32.5
|
||||
c0,19.1,11,32.9,27.7,32.9c16.8,0,27.5-13.7,27.5-32.9C684.2,438.1,673.8,424.2,656.8,424.2z"/>
|
||||
<path class="st11" d="M813.4,456.7c0,28.4-19.7,49.5-47.6,49.5c-27.8,0-47.6-21.1-47.6-49.5c0-28.2,19.7-49.3,47.6-49.3
|
||||
C793.7,407.4,813.4,428.4,813.4,456.7z M794.8,456.7c0-21.5-13.5-33.1-29-33.1c-15.5,0-29,11.6-29,33.1c0,21.7,13.5,33.3,29,33.3
|
||||
C781.3,489.9,794.8,478.3,794.8,456.7z"/>
|
||||
<path class="st11" d="M836.6,503.3v-140h18.2v140H836.6z"/>
|
||||
<path class="st11" d="M908.2,450.1l25.1-3.7c5.6-0.8,7.2-3.7,7.2-7c0-9.1-6-16.6-20.1-16.6c-12.8,0-19.9,7.9-21.1,18.8l-17.4-4.1
|
||||
c1.9-17.8,18-30.2,38.1-30.2c27.8,0,38.7,15.9,38.7,34V488c0,8.1,0.8,13,1.2,15.3H942c-0.4-2.3-1-5.8-1-12.6
|
||||
c-4.1,6.6-13.3,15.5-30,15.5c-19,0-31.1-13.1-31.1-27.8C879.9,461.7,892.1,452.4,908.2,450.1z M940.5,463.4v-4.3l-28.4,4.3
|
||||
c-7.7,1.2-13.5,5.6-13.5,13.9c0,7,5.8,13.5,15.1,13.5C928.5,490.9,940.5,483.7,940.5,463.4z"/>
|
||||
<path class="st11" d="M1038.5,428.6c-2.7-0.4-5.4-0.6-7.9-0.6c-15.3,0-25.5,8.1-25.5,28.6v46.6h-18.2v-93h17.8v16.2
|
||||
c6.8-14.3,18.4-17.8,27.8-17.8c2.5,0,5,0.4,6,0.6V428.6z"/>
|
||||
<path class="st11" d="M1066.7,361.9c7.3,0,13.2,5.8,13.2,13.1c0,7.2-5.8,13-13.2,13c-7.2,0-13-5.8-13-13
|
||||
C1053.8,367.7,1059.6,361.9,1066.7,361.9z M1057.8,503.3v-93h18v93H1057.8z"/>
|
||||
<path class="st11" d="M1114.5,473.3c1.2,9.7,8.5,17.4,21.7,17.4c10.2,0,15.9-5.8,15.9-12.4c0-5.8-4.3-10.2-12-12l-15.9-3.5
|
||||
c-14.5-3.1-23.2-13-23.2-26.1c0-15.9,14.9-29.4,33.1-29.4c25.5,0,33.5,16.6,35.4,24.9l-16.1,6c-0.8-4.8-4.6-15.5-19.3-15.5
|
||||
c-9.3,0-15.5,6-15.5,12.4c0,5.6,3.5,9.7,10.6,11.2l15.1,3.3c16.8,3.7,25.7,13.9,25.7,27.7c0,13.1-11,28.8-34,28.8
|
||||
c-25.5,0-36.4-16.4-37.9-27.1L1114.5,473.3z"/>
|
||||
</g>
|
||||
<circle class="st11" cx="765.9" cy="456.2" r="49.7"/>
|
||||
<circle class="st12" cx="787.6" cy="478.1" r="2.8"/>
|
||||
<circle class="st12" cx="744.8" cy="478.5" r="2.8"/>
|
||||
<g>
|
||||
<path class="st13" d="M750.9,431.5c0.6,0.8,1.2,1.6,1.7,2.3c3,3.8,5.9,7.7,8.9,11.5c0.3,0.3,0.3,0.8,0.2,1.1
|
||||
c-0.9,1.5-1.8,3-2.7,4.5c-1.3,2-2.5,4.2-3.8,6.2c0,0,0,0.1-0.1,0.2c0.6-0.4,1.2-0.7,1.7-1.1c3-1.8,6-3.7,9-5.5
|
||||
c0.4-0.2,0.8-0.1,1.3,0.2c2.8,2.1,5.6,4.4,8.4,6.5c1.7,1.3,3.2,2.5,4.9,3.8c0.1,0.1,0.2,0.2,0.4,0.2c-3.6-4.6-7.1-9.1-10.7-13.7
|
||||
c-0.3-0.4-0.4-0.8-0.2-1.1c1.1-1.8,2.2-3.6,3.3-5.4c1.1-1.7,2.2-3.6,3.2-5.4c-0.4,0.2-0.7,0.4-1.1,0.7c-2.2,1.4-4.5,2.7-6.7,4.1
|
||||
c-1,0.6-1.9,1.2-3,1.8c-0.4,0.3-0.8,0.2-1.3-0.2c-3.1-2.4-6.1-4.8-9.2-7.2C753.9,433.7,752.5,432.6,750.9,431.5
|
||||
C751,431.5,750.9,431.5,750.9,431.5z"/>
|
||||
<path class="st14" d="M772.7,439.7c-0.4,0.3-0.7,0.5-1.1,0.8c-1.8,1.3-3.6,2.5-5.4,3.8c-0.2,0.1-0.4,0.1-0.6,0
|
||||
c-0.9-0.6-1.7-1.3-2.6-1.8c-1.3-0.9-2.5-1.7-3.7-2.6c0,0,0,0-0.1-0.1c0.3,0.4,0.5,0.7,0.7,1.1c1.3,1.8,2.5,3.6,3.8,5.4
|
||||
c0.2,0.2,0.1,0.4,0,0.7c-1,1.4-1.8,2.6-2.8,4c-0.6,0.8-1.1,1.6-1.7,2.3c0,0.1-0.1,0.1-0.1,0.2c2.1-1.6,4.3-3,6.5-4.6
|
||||
c0.2-0.1,0.4-0.1,0.6,0c1.1,0.8,2.1,1.6,3.2,2.2c1.1,0.8,2.1,1.6,3.2,2.2c-0.2-0.2-0.3-0.4-0.5-0.7c-1-1.4-1.8-2.6-2.8-4
|
||||
c-0.4-0.6-0.9-1.2-1.3-1.7c-0.2-0.2-0.2-0.4,0-0.7c1.1-1.5,2-2.9,3.1-4.4C771.7,441,772.2,440.3,772.7,439.7L772.7,439.7z"/>
|
||||
<path class="st14" d="M765.9,434.5c-0.1,0.6-0.2,1.1-0.3,1.7c-0.5,2.7-1,5.4-1.4,8.2c0,0.2-0.3,0.5-0.5,0.5
|
||||
c-1.4,0.2-2.7,0.5-4,0.7c-1.8,0.3-3.7,0.7-5.6,1c0,0-0.1,0-0.2,0.1c0.6,0.1,1.1,0.2,1.6,0.3c2.7,0.5,5.4,1,8.2,1.5
|
||||
c0.3,0.1,0.5,0.3,0.6,0.6c0.4,2,0.7,4,1.1,6c0.2,1.2,0.4,2.3,0.6,3.5c0,0.1,0,0.2,0.1,0.3c0.6-3.3,1.2-6.5,1.7-9.8
|
||||
c0-0.3,0.3-0.5,0.5-0.5c1.7-0.3,3.2-0.6,4.9-0.9c1.7-0.3,3.2-0.6,4.9-0.9c-0.3-0.1-0.7-0.1-1-0.2c-2-0.4-4-0.7-6-1.1
|
||||
c-0.9-0.2-1.7-0.3-2.7-0.5c-0.4-0.1-0.6-0.2-0.6-0.6c-0.4-2.2-0.8-4.4-1.2-6.6C766.2,436.6,766.1,435.5,765.9,434.5L765.9,434.5z"
|
||||
/>
|
||||
<path class="st15" d="M765.9,439.7c-0.1,0.3-0.1,0.6-0.2,0.9c-0.3,1.6-0.5,3.1-0.8,4.7c0,0.1-0.2,0.3-0.3,0.3
|
||||
c-0.8,0.1-1.6,0.3-2.2,0.4c-1.1,0.2-2.1,0.4-3.2,0.6c0,0,0,0-0.1,0c0.3,0.1,0.6,0.1,0.9,0.2c1.6,0.3,3.1,0.5,4.7,0.8
|
||||
c0.2,0,0.3,0.2,0.3,0.3c0.2,1.2,0.4,2.2,0.6,3.4c0.1,0.7,0.2,1.4,0.3,1.9v0.1c0.3-1.8,0.7-3.7,1-5.5c0-0.2,0.1-0.3,0.3-0.3
|
||||
c0.9-0.2,1.8-0.3,2.7-0.5c0.9-0.2,1.8-0.3,2.7-0.5c-0.2,0-0.4-0.1-0.6-0.1c-1.2-0.2-2.2-0.4-3.4-0.6c-0.5-0.1-1-0.2-1.6-0.3
|
||||
c-0.2,0-0.3-0.1-0.4-0.3c-0.2-1.3-0.4-2.5-0.7-3.7C766.1,440.8,766,440.3,765.9,439.7L765.9,439.7z"/>
|
||||
</g>
|
||||
<path class="st16" d="M748.6,465.4l-13.1,10.8c-0.8,0.7-0.4,1.9,0.7,1.9h26.2c1.1,0,1.6-1.4,0.7-1.9l-13.1-10.8
|
||||
C749.5,465.1,749,465.1,748.6,465.4z"/>
|
||||
<path class="st16" d="M782.8,465.4l-13.1,10.8c-0.8,0.7-0.4,1.9,0.7,1.9h26.2c1.1,0,1.6-1.4,0.7-1.9l-13.1-10.8
|
||||
C783.7,465.1,783.1,465.1,782.8,465.4z"/>
|
||||
<path class="st17" d="M764.7,462.4l-23.1,18.9c-1.5,1.2-0.6,3.5,1.3,3.5h46.2c1.8,0,2.7-2.3,1.3-3.5l-23.1-18.9
|
||||
C766.4,461.8,765.4,461.8,764.7,462.4z"/>
|
||||
<path class="st18" d="M765,471.8l-17.7,14.5c-1.1,0.9-0.5,2.7,1,2.7h35.4c1.5,0,2-1.7,1-2.7l-17.8-14.5
|
||||
C766.3,471.3,765.5,471.3,765,471.8z"/>
|
||||
<path class="st17" d="M765.2,474.7l-14.8,12c-0.9,0.8-0.4,2.2,0.8,2.2h29.4c1.2,0,1.7-1.5,0.8-2.2l-14.6-12
|
||||
C766.3,474.3,765.7,474.3,765.2,474.7z"/>
|
||||
<circle class="st19" cx="765.9" cy="456.2" r="34.9"/>
|
||||
<g>
|
||||
<path class="st20" d="M1020.6,524.4c-2,0-3.4,1-3.9,2v-7.7h-2.3v18h2.3V535c0.7,1.3,2.1,2.1,3.9,2.1c3.5,0,5.6-2.8,5.6-6.3
|
||||
C1026,527.2,1024.2,524.4,1020.6,524.4z M1020.1,534.9c-2.1,0-3.5-1.7-3.5-4.3c0-2.6,1.5-4.2,3.5-4.2c2.2,0,3.5,1.6,3.5,4.2
|
||||
C1023.7,533.2,1022.3,534.9,1020.1,534.9z"/>
|
||||
<polygon class="st20" points="1033.2,533 1029.5,524.8 1026.8,524.8 1031.9,535.4 1028.9,541.7 1031.5,541.7 1039.2,524.8
|
||||
1036.7,524.8 "/>
|
||||
<path class="st20" d="M1046.8,537.4h2.1c1,0,1.8-0.8,1.8-1.8v-6.3h3c1.4,0,2.5-1.2,2.5-2.5v-1.1h-5.5v-4.4h6c1.2,0,2.2-1,2.2-2.2
|
||||
v-1.4h-12V537.4z"/>
|
||||
<path class="st20" d="M1065.8,522.9c-1.6,0-3,0.5-4,1.4c-1.1,0.9-1.6,2.1-1.7,3.6h3.6c0-0.5,0.2-0.9,0.6-1.2
|
||||
c0.4-0.3,0.9-0.5,1.5-0.5c0.6,0,1.2,0.1,1.5,0.5c0.3,0.4,0.5,0.8,0.5,1.5v0.5l-3.7,0.7c-0.7,0.1-1.3,0.2-1.8,0.4
|
||||
c-0.5,0.2-1,0.5-1.4,0.8c-0.5,0.3-0.8,0.7-1,1.2s-0.3,1-0.3,1.7c0,1.3,0.5,2.4,1.2,3c0.9,0.8,1.9,1.2,3.2,1.2
|
||||
c1.8,0,3.2-0.7,4.1-2.2v0.4c0.1,0.9,0.9,1.6,1.8,1.6h1.5v-9.3c0-1.7-0.5-3-1.5-3.9C1068.8,523.3,1067.5,522.9,1065.8,522.9z
|
||||
M1067.7,531.6c0,0.6-0.2,1.2-0.5,1.7c-0.3,0.4-0.6,0.7-1,0.9c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.2-0.3
|
||||
c-0.3-0.2-0.4-0.5-0.4-0.9c0-0.5,0.1-0.8,0.3-1c0.3-0.2,0.7-0.4,1.2-0.5l2.9-0.6V531.6z"/>
|
||||
<path class="st20" d="M1076.8,517c-0.6,0-1.1,0.2-1.5,0.6c-0.5,0.4-0.7,1-0.7,1.6c0,0.6,0.1,1.1,0.6,1.5c0.5,0.4,1,0.6,1.6,0.6
|
||||
c0.6,0,1.2-0.2,1.6-0.6c0.5-0.4,0.7-0.9,0.7-1.5c0-0.6-0.2-1.2-0.7-1.6C1078,517.2,1077.4,517,1076.8,517z"/>
|
||||
<path class="st20" d="M1075,537.4h1.9c1,0,1.8-0.8,1.8-1.8v-12.4h-3.7V537.4z"/>
|
||||
<path class="st20" d="M1085.8,525.4l-0.4-2.2h-3.2v14.2h2c1,0,1.8-0.8,1.8-1.8v-5.5c0-1.3,0.4-2.1,1.1-2.7
|
||||
c0.7-0.6,1.7-0.9,2.8-0.9V523c-1.1,0-2,0.2-2.7,0.7C1086.6,524.1,1086.1,524.7,1085.8,525.4z"/>
|
||||
<path class="st20" d="M1107.1,531.9l-0.3,1.5l-0.3-1.5l-2.4-8.5h-3.3l-2.4,8.5l-0.3,1.5l-0.3-1.5l-2.2-8.5h-3.9l4.3,14.1h2.3
|
||||
c0.8,0,1.5-0.5,1.8-1.3l2.3-7l2.6,8.3h2.5c0.8,0,1.6-0.5,1.8-1.3l3.9-12.8h-3.9L1107.1,531.9z"/>
|
||||
<path class="st20" d="M1117.2,517c-0.6,0-1.2,0.2-1.6,0.6c-0.5,0.4-0.7,0.9-0.7,1.5c0,0.7,0.2,1.2,0.7,1.5c0.5,0.4,1,0.6,1.6,0.6
|
||||
c0.6,0,1.1-0.2,1.6-0.6c0.5-0.4,0.7-0.9,0.7-1.5c0-0.6-0.3-1.1-0.7-1.5C1118.3,517.2,1117.8,517,1117.2,517z"/>
|
||||
<path class="st20" d="M1115.3,537.4h1.9c1,0,1.8-0.8,1.8-1.8v-12.4h-3.7V537.4z"/>
|
||||
<path class="st20" d="M1130.1,522.9c-1,0-1.7,0.2-2.4,0.6c-0.6,0.4-1.1,0.9-1.5,1.6l-0.3-1.9h-3.2v14.2h1.9c1,0,1.9-0.8,1.9-1.9
|
||||
V530c0-1.2,0.2-2,0.7-2.6s1-0.9,1.8-0.9c0.6,0,1.2,0.3,1.5,0.7c0.3,0.4,0.5,1.1,0.5,1.9v8.3h1.8c1,0,1.9-0.8,1.9-1.9v-7.3
|
||||
c0-1.7-0.4-2.9-1.2-3.9C1132.7,523.4,1131.6,522.9,1130.1,522.9z"/>
|
||||
<path class="st20" d="M1148.1,524.8c-0.4-0.5-0.9-0.9-1.7-1.3c-0.8-0.3-1.6-0.5-2.6-0.5c-1.9,0-3.5,0.7-4.8,2.1
|
||||
c-1.3,1.4-1.9,3.2-1.9,5.3c0,1.4,0.3,2.6,0.8,3.8c0.6,1.2,1.4,2,2.4,2.7c1,0.7,2.2,1,3.4,1c2.1,0,3.6-0.7,4.6-2.2l0.1,0.4
|
||||
c0.2,0.8,0.9,1.5,1.8,1.5h1.6v-20.4h-3.7V524.8z M1148,530.4c0,1.2-0.4,2.2-1.1,2.9c-0.7,0.7-1.5,1.1-2.5,1.1
|
||||
c-1.1,0-1.9-0.3-2.6-1.1c-0.7-0.7-1-1.7-1-2.9s0.3-2.2,1-2.9s1.6-1.1,2.6-1.1c0.6,0,1.2,0.1,1.7,0.4c0.5,0.2,0.9,0.7,1.3,1.3
|
||||
c0.4,0.6,0.6,1.3,0.6,2.1V530.4z"/>
|
||||
<path class="st20" d="M1164.8,530.4c-0.7-0.7-1.9-1.2-3.6-1.5c-1-0.2-1.6-0.4-2-0.6c-0.3-0.2-0.5-0.5-0.5-1c0-0.4,0.1-0.7,0.4-1
|
||||
c0.2-0.3,0.6-0.4,1.1-0.4c1,0,1.6,0.5,1.6,1.6h3.6c0-1.4-0.5-2.5-1.5-3.3c-0.9-0.8-2.2-1.2-3.7-1.2c-1.7,0-3,0.4-3.9,1.3
|
||||
c-0.9,0.9-1.4,2-1.4,3.3c0,1.2,0.3,2.1,1.1,2.8c0.7,0.6,1.8,1.1,3.4,1.4c0.9,0.2,1.6,0.4,2,0.7c0.3,0.3,0.5,0.6,0.5,1.1
|
||||
c0,0.4-0.2,0.8-0.5,1c-0.3,0.3-0.7,0.4-1.3,0.4c-0.6,0-1.1-0.2-1.5-0.5c-0.1-0.1-0.2-0.2-0.3-0.3c-0.4-0.6-0.9-1-1.6-1h-2.3
|
||||
c0.1,1.4,0.6,2.6,2.1,3.5c0.9,0.8,2.3,1.2,4,1.2c0.8,0,1.4-0.1,2.1-0.3c0.6-0.1,1.2-0.4,1.7-0.8c0.5-0.3,0.9-0.8,1.2-1.4
|
||||
s0.4-1.3,0.4-2.1C1165.9,532,1165.5,531.1,1164.8,530.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<radialGradient id="SVGID_1_" cx="1402.3696" cy="470.6803" r="16.1424" fx="1402.6445" fy="471.3849" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#FFF7F2"/>
|
||||
<stop offset="1" style="stop-color:#6D6D6D"/>
|
||||
</radialGradient>
|
||||
<circle class="st27" cx="1402.4" cy="470.7" r="16.1"/>
|
||||
<path class="st1" d="M1402.4,442.2c17.3,0,29.6,12.1,29.6,28.4c0,16.3-12.3,28.5-29.6,28.5s-29.6-12.1-29.6-28.5
|
||||
S1385.1,442.2,1402.4,442.2z M1422,470.4c0-10.9-8.8-19.7-19.7-19.7c-10.9,0-19.7,8.8-19.7,19.7c0,10.9,8.8,19.7,19.7,19.7
|
||||
c0,0,0,0,0,0C1413.2,490,1422,481.2,1422,470.4L1422,470.4z"/>
|
||||
<radialGradient id="SVGID_2_" cx="1402.3696" cy="470.6803" r="21.6422" fx="1402.7382" fy="471.625" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#FFF7F2"/>
|
||||
<stop offset="1" style="stop-color:#6D6D6D"/>
|
||||
</radialGradient>
|
||||
<polygon class="st28" points="1406.9,470.7 1406.9,470.6 1406.9,470.7 1425.5,450.6 1402.4,466.2 1402.4,466.2 1379.2,450.6
|
||||
1397.8,470.7 1379.2,490.7 1402.4,475.2 1402.3,475.1 1402.4,475.1 1402.4,475.2 1425.5,490.7 1406.9,470.7 "/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 486.5 92.3" style="enable-background:new 0 0 486.5 92.3;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M72.5,24.3H0.4V0.4h84.4V12C84.8,18.8,79.3,24.3,72.5,24.3z"/>
|
||||
<path class="st0" d="M41.8,58H0.4V34.1h53.7v11.6C54.1,52.5,48.6,58,41.8,58z"/>
|
||||
<path class="st0" d="M12,91.7H0.4V67.8h23.9v11.6C24.2,86.2,18.7,91.7,12,91.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M121.7,77V16.9h36.9v4.2c0,3.7-3,6.8-6.8,6.8h-18.4v13.5H150v3.5c0,4.1-3.3,7.5-7.5,7.5h-9.1v19.1 c0,3-2.5,5.5-5.5,5.5H121.7z"/>
|
||||
<path class="st0" d="M161.1,64.7c0-2,0.3-3.7,1-5.1c0.7-1.5,1.6-2.7,2.9-3.6c1.3-1,2.7-1.7,4.2-2.3c1.6-0.6,3.3-1,5.4-1.3l11.3-2 v-1.5c0-2-0.5-3.5-1.6-4.6c-1-1.1-2.6-1.6-4.5-1.6s-3.4,0.5-4.6,1.4c-1.2,1-1.8,2.2-1.8,3.8h-10.8c0.3-4.5,1.9-8.2,5.1-11 c3.1-2.8,7.2-4.3,12.3-4.3c5.3,0,9.5,1.3,12.6,4s4.7,6.6,4.7,11.8V77h-4.7c-2.8,0-5.1-2.1-5.5-4.8l-0.1-1.1 c-2.7,4.6-6.9,6.8-12.4,6.8c-3.9,0-7.1-1.2-9.6-3.6C162.4,72,161.1,68.8,161.1,64.7z M172.3,64c0,1.2,0.4,2.1,1.3,2.8 c0.9,0.7,2.1,1,3.7,1c1.4,0,2.7-0.3,3.9-0.9c1.2-0.6,2.2-1.5,3-2.7c1-1.4,1.5-3.2,1.5-5.3v-1.2l-8.7,1.8c-1.6,0.3-2.8,0.8-3.6,1.5 C172.7,61.8,172.3,62.7,172.3,64z"/>
|
||||
<path class="st0" d="M207,20.9c0-1.8,0.7-3.6,2-4.9c1.3-1.2,2.8-1.9,4.6-1.9c1.8,0,3.6,0.6,4.9,1.9c1.4,1.3,2.1,2.9,2.1,4.8 s-0.7,3.4-2,4.7c-1.3,1.3-2.9,1.9-4.8,1.9s-3.5-0.6-4.8-1.9C207.7,24.2,207.1,22.7,207,20.9z M208.2,77V33.5h11.2v38 c0,3-2.5,5.5-5.5,5.5H208.2z"/>
|
||||
<path class="st0" d="M230.4,77V33.5h9.6l1.1,6.8c0.8-2.1,2.3-3.9,4.4-5.2c2.1-1.4,4.9-2.1,8.3-2.1v11c-3.5,0-6.4,0.9-8.6,2.6 s-3.4,4.4-3.5,8.1v16.7c0,3-2.5,5.5-5.5,5.5L230.4,77L230.4,77z"/>
|
||||
<path class="st0" d="M259.6,33.5h11.9l6.6,26.1l0.8,4.6l1-4.6l7.4-26.1h9.9l7.4,26.1l0.9,4.6l0.8-4.6l6.7-26.1h11.9L313,72.9 c-0.7,2.4-3,4.1-5.5,4.1H300l-7.9-25.6L285,73c-0.8,2.3-3,3.9-5.4,3.9h-7L259.6,33.5z"/>
|
||||
<path class="st0" d="M330.5,20.7c0-1.8,0.7-3.4,2-4.7c1.3-1.3,2.9-1.9,4.8-1.9c1.8,0,3.4,0.6,4.8,1.9c1.3,1.3,2,2.8,2,4.7 c0,1.9-0.7,3.4-2,4.7c-1.3,1.3-2.9,1.9-4.8,1.9s-3.5-0.6-4.8-1.9C331.2,24.2,330.5,22.6,330.5,20.7z M331.7,77V33.5h11.2v38 c0,3-2.5,5.5-5.5,5.5H331.7z"/>
|
||||
<path class="st0" d="M354.8,77V33.5h9.6l0.9,5.8c1.1-2,2.6-3.6,4.6-4.9c2-1.2,4.4-1.9,7.2-1.9c4.5,0,8,1.4,10.4,4.3 s3.6,6.8,3.6,11.9v22.5c0,3.2-2.6,5.7-5.7,5.7h-5.5V51.4c0-2.5-0.5-4.5-1.6-5.8c-1-1.3-2.6-2-4.6-2c-2.3,0-4.1,0.9-5.5,2.7 c-1.4,1.8-2.2,4.4-2.2,7.9v17.1c0,3.2-2.6,5.7-5.7,5.7L354.8,77L354.8,77z"/>
|
||||
<path class="st0" d="M399.1,55.2c0-6.4,1.9-11.8,5.7-16.1c3.8-4.3,8.7-6.5,14.5-6.5c2.9,0,5.5,0.5,7.8,1.6s4,2.4,5.2,4.1V14.4 h11.2V77h-4.8c-2.7,0-5-2-5.4-4.7l-0.2-1.2c-3.1,4.5-7.7,6.8-13.9,6.8c-3.8,0-7.3-1-10.4-3s-5.5-4.7-7.2-8.2 C399.9,63.3,399.1,59.4,399.1,55.2z M410.5,55.2c0,3.7,1,6.6,3.1,8.9c2.1,2.3,4.7,3.4,7.8,3.4c2.9,0,5.5-1.1,7.7-3.2 c2.2-2.2,3.3-5.1,3.3-8.8V55c0-2.5-0.6-4.6-1.7-6.5s-2.5-3.2-4.1-4.1s-3.4-1.3-5.2-1.3c-3.1,0-5.7,1.1-7.8,3.4 S410.5,51.6,410.5,55.2z"/>
|
||||
<path class="st0" d="M452.2,63.8h6.9c2,0,3.7,1.2,4.8,2.9c0.2,0.4,0.6,0.7,0.9,1c1.1,0.9,2.6,1.4,4.5,1.4c1.7,0,3-0.4,4-1.1 c0.9-0.7,1.4-1.8,1.4-3c0-1.4-0.5-2.5-1.6-3.3s-3.2-1.5-6.2-2.1c-4.8-0.8-8.3-2.3-10.5-4.4c-2.3-2.1-3.4-4.9-3.4-8.5 c0-4,1.4-7.4,4.2-10.1c2.8-2.7,6.8-4.1,11.9-4.1c4.6,0,8.4,1.3,11.3,3.8c2.9,2.5,4.5,5.8,4.7,9.9H474c-0.1-3.3-1.7-5-5-5 c-1.5,0-2.7,0.4-3.5,1.2c-0.8,0.8-1.3,1.8-1.3,3.1c0,1.4,0.5,2.4,1.6,3.1s3.1,1.3,6,1.8c5.1,0.9,8.8,2.4,10.9,4.5 c2.2,2.1,3.2,5,3.2,8.7c0,2.4-0.4,4.5-1.3,6.4c-0.9,1.8-2.1,3.3-3.7,4.4c-1.6,1.1-3.3,2-5.3,2.5c-2,0.6-4.1,0.8-6.5,0.8 c-5.3,0-9.4-1.3-12.3-3.8C453.9,71.6,452.3,68.2,452.2,63.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
var llcookieless = true;
|
||||
var sf14gv = 32793;
|
||||
(function() {
|
||||
var sf14g = document.createElement('script');
|
||||
sf14g.src = 'https://lltrck.com/lt-v2.min.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(sf14g, s);
|
||||
})();
|
||||
|
||||
!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';
|
||||
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');
|
||||
|
||||
(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');
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
setTimeout(function() {
|
||||
var link = document.getElementsByClassName('home-link')[0];
|
||||
linkClone = link.cloneNode(true);
|
||||
linkClone.href = "https://fairwinds.com";
|
||||
link.setAttribute('target', '_blank');
|
||||
link.parentNode.replaceChild(linkClone, link);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
.github-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $primaryColor;
|
||||
}
|
||||
.text-danger {
|
||||
color: $dangerColor;
|
||||
}
|
||||
.text-warning {
|
||||
color: $warningColor;
|
||||
}
|
||||
.text-info {
|
||||
color: $infoColor;
|
||||
}
|
||||
.text-success {
|
||||
color: $successColor;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.2rem solid $warningColor;
|
||||
}
|
||||
blockquote p {
|
||||
color: $warningColor;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom),
|
||||
.page-nav,
|
||||
.page-edit,
|
||||
footer {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom) > h2 {
|
||||
padding-top: 7rem;
|
||||
}
|
||||
|
||||
.navbar .site-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar, .navbar .links {
|
||||
background-color: $primaryColor !important;
|
||||
}
|
||||
|
||||
.navbar .links a {
|
||||
color: #fff;
|
||||
}
|
||||
.navbar .links a svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 5px solid #f7f7f7;
|
||||
}
|
||||
|
||||
.no-border img,
|
||||
img.no-border,
|
||||
header img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mini-img {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom) .mini-img img {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
|
||||
$primaryColor = #23103A
|
||||
$dangerColor = #A0204C
|
||||
$warningColor = #FF6C00
|
||||
$infoColor = #8BD2DC
|
||||
$successColor = #28a745
|
||||
|
||||
$accentColor = #FF6C00
|
||||
$textColor = #2c3e50
|
||||
$borderColor = #eaecef
|
||||
$codeBgColor = #282c34
|
||||
$arrowBgColor = #ccc
|
||||
$badgeTipColor = #42b983
|
||||
$badgeWarningColor = darken(#ffe564, 35%)
|
||||
$badgeErrorColor = #DA5961
|
||||
|
||||
// layout
|
||||
$navbarHeight = 3.6rem
|
||||
$sidebarWidth = 20rem
|
||||
$contentWidth = 740px
|
||||
$homePageWidth = 960px
|
||||
|
||||
// responsive breakpoints
|
||||
$MQNarrow = 959px
|
||||
$MQMobile = 719px
|
||||
$MQMobileNarrow = 419px
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extend: '@vuepress/theme-default'
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<ParentLayout>
|
||||
<template #page-bottom>
|
||||
<div class="custom-footer">
|
||||
<div class="left-footer">
|
||||
<a href="https://fairwinds.com" target="_blank">Learn more about Fairwinds</a>
|
||||
<a href="https://fairwinds.com/insights" target="_blank">Try Fairwinds Insights</a>
|
||||
</div>
|
||||
<div class="right-footer">
|
||||
<a href="https://www.fairwinds.com/privacy-policy" target="_blank">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ParentLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ParentLayout from '@parent-theme/layouts/Layout.vue'
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
|
||||
components: {
|
||||
ParentLayout
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div.custom-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid #eaecef;
|
||||
padding: 2rem 2.5rem;
|
||||
}
|
||||
.custom-footer .left-footer {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.custom-footer .left-footer a:first-of-type {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
.custom-footer a {
|
||||
color: #4e6e8e;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<div align="center" class="no-border">
|
||||
<img src="/img/polaris-logo.png" alt="Polaris Logo">
|
||||
<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">
|
||||
</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.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.
|
||||
|
||||
Polaris can be run in three different modes:
|
||||
* As a [dashboard](/dashboard), so you can audit what's running inside your cluster.
|
||||
* As an [admission controller](/admission-controller), so you can automatically reject workloads that don't adhere to your organization's policies.
|
||||
* As a [command-line tool](/infrastructure-as-code), so you can test local YAML files, e.g. as part of a CI/CD process.
|
||||
|
||||
<p align="center">
|
||||
<img src="/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)
|
||||
|
||||
|
||||
|
||||
## Integration with Fairwinds Insights
|
||||
<p align="center">
|
||||
<img src="/img/FW_Insights_Polaris.svg" alt="Fairwinds Insights" width="550"/>
|
||||
</p>
|
||||
|
||||
[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)
|
||||
|
||||
you can sign up for a [free account here](https://insights.fairwinds.com?source=polaris).
|
||||
|
||||
## Contributing
|
||||
PRs welcome! Check out the [Contributing Guidelines](/contributing) and [Code of Conduct](/code-of-conduct) for more information.
|
||||
|
||||
## Further Information
|
||||
A history of changes to this project can be viewed in the [Changelog](/changelog)
|
||||
|
||||
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="/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
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)
|
||||
|
||||
Polaris can be run as an admission controller that acts as a validating webhook.
|
||||
This accepts the same configuration as the dashboard, and can run the same validations.
|
||||
|
||||
The webhook will reject any workloads that trigger a danger-level check.
|
||||
This is indicative of the greater goal of Polaris, not just to encourage better
|
||||
configuration through dashboard visibility, but to actually enforce it with this webhook.
|
||||
|
||||
Note that Polaris will not alter your workloads, only block workloads that don't conform to the configured policies.
|
||||
|
||||
## Installation
|
||||
A valid TLS certificate is required for the Polaris Validating Webhook. If you have cert-manager installed in your cluster then the install methods below will work.
|
||||
|
||||
If you don't use cert-manager, you'll need to:
|
||||
|
||||
* Supply a CA Bundle with the `webhook.caBundle`
|
||||
* 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 \
|
||||
--set webhook.enable=true --set dashboard.enable=false
|
||||
```
|
||||
|
||||
## Workload Types
|
||||
The webhook comes with built-in support for a handful of known controller types,
|
||||
such as Deployments, Jobs, and DaemonSets. To add new controller types,
|
||||
you can set `webhook.rules` in the
|
||||
[Helm chart](https://github.com/FairwindsOps/charts/tree/master/stable/polaris)
|
||||
|
||||
## Warnings
|
||||
Unfortunately we have not found a way to display warnings as part of `kubectl`
|
||||
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.
|
||||
@@ -1,18 +1,113 @@
|
||||
# x.x.x (next release)
|
||||
---
|
||||
sidebarDepth: 0
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Changelog"
|
||||
|
||||
# 1.2.1
|
||||
---
|
||||
## 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
|
||||
JSON schema (see changes to `./checks/multipleReplicasForDeployment.yaml`)
|
||||
|
||||
## 2.0.1
|
||||
* Fixed Polaris deployment process
|
||||
|
||||
## 2.0.0
|
||||
* Standardize categories of checks into Security, Reliability, and Efficiency
|
||||
* Changes to the dashboard UI
|
||||
* Update controller-runtime
|
||||
|
||||
## 1.2.1
|
||||
* Update date on dashboard footer
|
||||
|
||||
# 1.2.0
|
||||
## 1.2.0
|
||||
* Add ability to audit a single workload
|
||||
* Enable `pullPolicyAlways` by default
|
||||
* Fix for finding parent resources
|
||||
|
||||
# 1.1.1
|
||||
## 1.1.1
|
||||
* Show controller checks on dashboard
|
||||
* Fix for orphaned pods w/ controller checks
|
||||
|
||||
# 1.1.0
|
||||
## 1.1.0
|
||||
* Add namespace filter in UI
|
||||
* Add priorityClass check
|
||||
* Support reading from STDIN
|
||||
@@ -22,92 +117,92 @@
|
||||
* Fixed case where parent resources trigger error
|
||||
* Fixed UI zero-state
|
||||
|
||||
# 1.0.3
|
||||
## 1.0.3
|
||||
* Fixed case where parent resources trigger error
|
||||
* Fixed dashboard link when `--base-path` is set
|
||||
|
||||
# 1.0.2
|
||||
## 1.0.2
|
||||
* Fixed case where custom CRDs are not covered by RBAC
|
||||
|
||||
# 1.0.1
|
||||
## 1.0.1
|
||||
* Added ARM binaries to releases
|
||||
|
||||
# 1.0.0
|
||||
## New Features
|
||||
## 1.0.0
|
||||
### New Features
|
||||
* Added support for custom checks using JSON Schema
|
||||
* Added support for arbitrary controllers, rather than a pre-configured set
|
||||
* removed support for `controllers_to_scan` in config
|
||||
* Added the ability to exempt a particular controller from a particular check.
|
||||
* Docker image now includes the default config
|
||||
|
||||
## Breaking Changes
|
||||
* Breaking changes in both input and output formats. See [Examples](/examples) for examples of the new formats.
|
||||
### Breaking Changes
|
||||
* Breaking changes in both input and output formats. See [Examples](https://github.com/FairwindsOps/polaris/tree/master/examples) for examples of the new formats.
|
||||
* removed config-level configuration for checks like max/min memory settings
|
||||
* changed severity `error` to `danger`
|
||||
* Breaking changes [to the CLI](/docs/usage.md#cli-options)
|
||||
* Breaking changes to the CLI
|
||||
* CLI flag `--set-exit-code-on-error` is now `--set-exit-code-on-danger`
|
||||
* Flags `--version`, `--dashboard`, `--webhook`, and `--audit` are now arguments
|
||||
* Port flags are now just `--port`
|
||||
|
||||
# 0.6.0
|
||||
## 0.6.0
|
||||
* Fixed webhook support in Kubernetes 1.16
|
||||
* this also removes support for 1.8
|
||||
* Added support for exemptions via controller annotations
|
||||
|
||||
# 0.5.2
|
||||
## 0.5.2
|
||||
* Fixed missing success messages for resource requests/limits
|
||||
|
||||
# 0.5.1
|
||||
## 0.5.1
|
||||
* Added a few more exemptions
|
||||
* Started checking exemptions based on controller name prefix
|
||||
* `runAsUser != 0` now passes the `runAsNonRoot` check
|
||||
|
||||
# 0.5.0
|
||||
## 0.5.0
|
||||
* Added `--load-audit-file` flag to run the dashboard from an existing audit
|
||||
* Added an `ID` field to each check in the output
|
||||
* Skip health checks for jobs, cronjobs, initcontainers
|
||||
* Added support for exemptions
|
||||
* Fixed dashboard base path option
|
||||
|
||||
# 0.4.0
|
||||
## 0.4.0
|
||||
* Added additional Pod Controllers to scan PodSpec (`jobs`, `cronjobs`, `daemonsets`, `replicationcontrollers`)
|
||||
|
||||
# 0.3.1
|
||||
## 0.3.1
|
||||
* Changed dashboard branding to refer to new org name Fairwinds
|
||||
|
||||
# 0.3.0
|
||||
## 0.3.0
|
||||
* Added `--set-exit-code-on-error` and `--set-exit-code-below-score` flags to better support CI/CD
|
||||
|
||||
# 0.2.1
|
||||
## 0.2.1
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/146): Fixed logic on RunAsNonRoot check to incorporate settings in podSpec
|
||||
|
||||
# 0.2.0
|
||||
## 0.2.0
|
||||
* Added `--output-format` flag for better CI/CD support
|
||||
* Added `--display-name` flag
|
||||
* Added support for StatefulSets
|
||||
* Show error message if no kubeconfig is set
|
||||
|
||||
# 0.1.5
|
||||
## 0.1.5
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/125): ignore limits/requests for initContainers
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/132): support custom base path
|
||||
|
||||
# 0.1.4
|
||||
## 0.1.4
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/116): details pages getting template errors
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/114): support all auth providers
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/112): Ignore readiness probe for initContainers
|
||||
|
||||
# 0.1.3
|
||||
## 0.1.3
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/109): dashboard not updating when running persistently
|
||||
|
||||
# 0.1.2
|
||||
## 0.1.2
|
||||
* Stored all third-party assets (e.g. Charts.js) to local files to support offline dashboard viewing
|
||||
* Fix: custom configs in `ConfigMap` not respected
|
||||
|
||||
# 0.1.1
|
||||
## 0.1.1
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/93): missing `config.yaml` and dashboard assets in binary releases
|
||||
* Added some tests and better error handling
|
||||
|
||||
# 0.1.0
|
||||
## 0.1.0
|
||||
* Dashboard fully functional
|
||||
* Validating webhook functional, but still considered beta
|
||||
* Checks:
|
||||
@@ -1,22 +0,0 @@
|
||||
# Health Checks
|
||||
|
||||
Polaris supports validating the presence of readiness and liveness probes in pods.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`healthChecks.readinessProbeMissing` | `warning` | Fails when a readiness probe is not configured for a pod.
|
||||
`healthChecks.livenessProbeMissing` | `warning` | Fails when a liveness probe is not configured for a pod.
|
||||
|
||||
## Background
|
||||
|
||||
Readiness and liveness probes can help maintain the health of applications running inside Kubernetes. By default, Kubernetes only knows whether or not a process is running, not if it's healthy. Properly configured readiness and liveness probes will also be able to ensure the health of an application.
|
||||
|
||||
Readiness probes are designed to ensure that an application has reached a "ready" state. In many cases there is a period of time between when a webserver process starts and when it is ready to receive traffic. A readiness probe can ensure the traffic is not sent to a pod until it is actually ready to receive traffic.
|
||||
|
||||
Liveness probes are designed to ensure that an application stays in a healthy state. When a liveness probe fails, the pod will be restarted.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Kubernetes Docs: Configure Liveness and Readiness Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)
|
||||
- [Utilizing Kubernetes Liveness and Readiness Probes to Automatically Recover From Failure](https://medium.com/spire-labs/utilizing-kubernetes-liveness-and-readiness-probes-to-automatically-recover-from-failure-2fe0314f2b2e)
|
||||
- [Kubernetes Liveness and Readiness Probes: How to Avoid Shooting Yourself in the Foot](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/)
|
||||
@@ -1,19 +0,0 @@
|
||||
# Images
|
||||
|
||||
Polaris supports a number of checks related to the image specified by pods.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`images.tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`images.pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
|
||||
## Background
|
||||
|
||||
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
|
||||
|
||||
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [What's Wrong With The Docker :latest Tag?](https://vsupalov.com/docker-latest-tag/)
|
||||
- [Kubernetes’ AlwaysPullImages Admission Control — the Importance, Implementation, and Security Vulnerability in its Absence](https://medium.com/@trstringer/kubernetes-alwayspullimages-admission-control-the-importance-implementation-and-security-d83ff3815840)
|
||||
@@ -1,22 +0,0 @@
|
||||
# Networking
|
||||
|
||||
Polaris supports a number of checks related to pod networking.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`networking.hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
|
||||
`networking.hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
|
||||
|
||||
|
||||
## Background
|
||||
|
||||
Although Kubernetes allows you to deploy a pod with access to the host network namespace, it's rarely a good idea. A pod running with the `hostNetwork` attribute enabled will have access to the loopback device, services listening on localhost, and could be used to snoop on network activity of other pods on the same node. There are certain examples where setting `hostNetwork` to true is required, such as deploying a networking plugin like Flannel.
|
||||
|
||||
Setting the `hostPort` attribute on a container will ensure that it is accessible on that specific port on each node it is deployed to. Unfortunately when this is specified, it limits where a pod can actually be scheduled in a cluster.
|
||||
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Kubernetes Docs: Configuration Best Practices](https://kubernetes.io/docs/concepts/configuration/overview/#services)
|
||||
|
||||
- [Accessing Kubernetes Pods from Outside of the Cluster](http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/)
|
||||
@@ -1,27 +0,0 @@
|
||||
# Security
|
||||
|
||||
Polaris supports a number of checks related to security.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`security.hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
|
||||
`security.hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
|
||||
`security.notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
|
||||
`security.privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
|
||||
`security.runAsRootAllowed` | `danger` | Fails when `securityContext.runAsNonRoot` is not true.
|
||||
`security.runAsPrivileged` | `danger` | Fails when `securityContext.privileged` is true.
|
||||
`security.insecureCapabilities` | `warning` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](/checks/insecureCapabilities.yaml)
|
||||
`security.dangerousCapabilities` | `danger` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](/checks/dangerousCapabilities.yaml)
|
||||
|
||||
## Background
|
||||
|
||||
Securing workloads in Kubernetes is an important part of overall cluster security. The overall goal should be to ensure that containers are running with as minimal privileges as possible. This includes avoiding privilege escalation, not running containers with a root user, and using read only file systems wherever possible.
|
||||
|
||||
Much of this configuration can be found in the `securityContext` attribute for both Kubernetes pods and containers. Where configuration is available at both a pod and container level, Polaris validates both.
|
||||
|
||||
## Further Reading
|
||||
- [Kubernetes Docs: Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
|
||||
- [KubeCon 2018 Keynote: Running with Scissors](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||
- [Kubernetes Security Book](https://kubernetes-security.info/)
|
||||
- [Kubernetes Docs: Set capabilities for a Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container)
|
||||
- [Linux Programmer's Manual: Capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html)
|
||||
@@ -1,6 +1,12 @@
|
||||
# Resources
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ensure that CPU and memory settings are configured, so that Kubernetes can schedule your workload effectively"
|
||||
---
|
||||
# Efficiency
|
||||
|
||||
Polaris supports a number of checks related to CPU and Memory requests and limits.
|
||||
These checks ensure that CPU and memory settings are configured, so that
|
||||
Kubernetes can schedule your workload effectively.
|
||||
|
||||
## Presence Checks
|
||||
|
||||
@@ -8,10 +14,10 @@ To simplify ensure that these values have been set, the following attributes are
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`resources.cpuRequestsMissing` | `danger` | Fails when `resources.requests.cpu` attribute is not configured.
|
||||
`resources.memoryRequestsMissing` | `danger` | Fails when `resources.requests.memory` attribute is not configured.
|
||||
`resources.cpuLimitsMissing` | `danger` | Fails when `resources.limits.cpu` attribute is not configured.
|
||||
`resources.memoryLimitsMissing` | `danger` | Fails when `resources.limits.memory` attribute is not configured.
|
||||
`cpuRequestsMissing` | `warning` | Fails when `resources.requests.cpu` attribute is not configured.
|
||||
`memoryRequestsMissing` | `warning` | Fails when `resources.requests.memory` attribute is not configured.
|
||||
`cpuLimitsMissing` | `warning` | Fails when `resources.limits.cpu` attribute is not configured.
|
||||
`memoryLimitsMissing` | `warning` | Fails when `resources.limits.memory` attribute is not configured.
|
||||
|
||||
## Background
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
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,
|
||||
and are running the correct image.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`readinessProbeMissing` | `warning` | Fails when a readiness probe is not configured for a pod.
|
||||
`livenessProbeMissing` | `warning` | Fails when a liveness probe is not configured for a pod.
|
||||
`tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
`priorityClassNotSet` | `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`
|
||||
|
||||
## Background
|
||||
|
||||
Readiness and liveness probes can help maintain the health of applications running inside Kubernetes. By default, Kubernetes only knows whether or not a process is running, not if it's healthy. Properly configured readiness and liveness probes will also be able to ensure the health of an application.
|
||||
|
||||
Readiness probes are designed to ensure that an application has reached a "ready" state. In many cases there is a period of time between when a webserver process starts and when it is ready to receive traffic. A readiness probe can ensure the traffic is not sent to a pod until it is actually ready to receive traffic.
|
||||
|
||||
Liveness probes are designed to ensure that an application stays in a healthy state. When a liveness probe fails, the pod will be restarted.
|
||||
|
||||
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
|
||||
|
||||
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [What's Wrong With The Docker :latest Tag?](https://vsupalov.com/docker-latest-tag/)
|
||||
- [Kubernetes’ AlwaysPullImages Admission Control — the Importance, Implementation, and Security Vulnerability in its Absence](https://medium.com/@trstringer/kubernetes-alwayspullimages-admission-control-the-importance-implementation-and-security-d83ff3815840)
|
||||
- [Kubernetes Docs: Configure Liveness and Readiness Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)
|
||||
- [Utilizing Kubernetes Liveness and Readiness Probes to Automatically Recover From Failure](https://medium.com/spire-labs/utilizing-kubernetes-liveness-and-readiness-probes-to-automatically-recover-from-failure-2fe0314f2b2e)
|
||||
- [Kubernetes Liveness and Readiness Probes: How to Avoid Shooting Yourself in the Foot](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/)
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Security Checks Documentation"
|
||||
---
|
||||
# Security
|
||||
|
||||
These checks are related to security concerns. Workloads that fail these
|
||||
checks may make your cluster more vulnerable, often by introducing a path
|
||||
for privilege escalation.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
|
||||
`hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
|
||||
`notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
|
||||
`privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
|
||||
`runAsRootAllowed` | `warning` | Fails when `securityContext.runAsNonRoot` is not true.
|
||||
`runAsPrivileged` | `danger` | Fails when `securityContext.privileged` is true.
|
||||
`insecureCapabilities` | `warning` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/checks/insecureCapabilities.yaml)
|
||||
`dangerousCapabilities` | `danger` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/checks/dangerousCapabilities.yaml)
|
||||
`hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
|
||||
`hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
|
||||
`tlsSettingsMissing` | `warning` | Fails when an Ingress lacks TLS settings.
|
||||
|
||||
## Background
|
||||
|
||||
Securing workloads in Kubernetes is an important part of overall cluster security. The overall goal should be to ensure that containers are running with as minimal privileges as possible. This includes avoiding privilege escalation, not running containers with a root user, not giving excessive access to the host network, and using read only file systems wherever possible.
|
||||
|
||||
A pod running with the `hostNetwork` attribute enabled will have access to the loopback device, services listening on localhost, and could be used to snoop on network activity of other pods on the same node. There are certain examples where setting `hostNetwork` to true is required, such as deploying a networking plugin like Flannel.
|
||||
|
||||
Setting the `hostPort` attribute on a container will ensure that it is accessible on that specific port on each node it is deployed to. Unfortunately when this is specified, it limits where a pod can actually be scheduled in a cluster.
|
||||
|
||||
Much of this configuration can be found in the `securityContext` attribute for both Kubernetes pods and containers. Where configuration is available at both a pod and container level, Polaris validates both.
|
||||
|
||||
## Further Reading
|
||||
- [Kubernetes Docs: Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
|
||||
- [KubeCon 2018 Keynote: Running with Scissors](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||
- [Kubernetes Security Book](https://kubernetes-security.info/)
|
||||
- [Kubernetes Docs: Set capabilities for a Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container)
|
||||
- [Linux Programmer's Manual: Capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html)
|
||||
- [Kubernetes Docs: Configuration Best Practices](https://kubernetes.io/docs/concepts/configuration/overview/#services)
|
||||
- [Accessing Kubernetes Pods from Outside of the Cluster](http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/)
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
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.
|
||||
--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.
|
||||
--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
|
||||
--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)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Code of Conduct"
|
||||
---
|
||||
# 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 [INSERT EMAIL ADDRESS]. 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/
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
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.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). Please review this document before contributing to this project.
|
||||
This project adheres to a [code of conduct](code-of-conduct.md). Please review this document before contributing to this project.
|
||||
|
||||
## Sign the CLA
|
||||
Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/fairwindsops/polaris).
|
||||
@@ -32,12 +37,26 @@ 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.
|
||||
@@ -50,7 +69,7 @@ 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
|
||||
@@ -83,10 +102,7 @@ The steps are:
|
||||
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:
|
||||
1. stable/polaris/README.md
|
||||
2. stable/polaris/Chart.yaml
|
||||
3. stable/polaris/values.yaml
|
||||
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
|
||||
@@ -104,4 +120,3 @@ The steps are:
|
||||
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
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
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.
|
||||
|
||||
Polaris validation checks fall into several different categories:
|
||||
|
||||
- [Security](/checks/security.md)
|
||||
- [Reliability](/checks/reliability.md)
|
||||
- [Efficiency](/checks/efficiency.md)
|
||||
|
||||
To change the default severity levels, or to turn checks on or off, you can create your own `config.yaml`:
|
||||
```yaml
|
||||
checks:
|
||||
tagNotSpecified: ignore
|
||||
runAsRootAllowed: danger
|
||||
pullPolicyNotAlways: warning
|
||||
```
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
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).
|
||||
|
||||
You can customize the configuration to do things like:
|
||||
* Turn checks [on and off](checks.md)
|
||||
* Change the [severity level](checks.md) of checks
|
||||
* Add new [custom checks](custom-checks.md)
|
||||
* Add [exemptions](exemptions.md) for particular workloads or namespaces
|
||||
|
||||
To pass in your custom configuration, follow the instructions for your environment:
|
||||
|
||||
* CLI - set the `--config` argument to point to your `config.yaml`
|
||||
* Helm - set the `config` variable in your values file
|
||||
* kubectl - create a ConfigMap with your `config.yaml`, mount it as a volume, and use the `--config` argument in your Deployment
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
---
|
||||
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:
|
||||
|
||||
```yaml
|
||||
checks:
|
||||
imageRegistry: warning
|
||||
|
||||
customChecks:
|
||||
imageRegistry:
|
||||
successMessage: Image comes from allowed registries
|
||||
failureMessage: Image should not be from disallowed registry
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
type: string
|
||||
not:
|
||||
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 contains a pod spec (e.g. Deployments, CronJobs, StatefulSets), as well as naked Pods
|
||||
* `Pod`, 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`, `Pod` 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 }}"
|
||||
```
|
||||
|
||||
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:
|
||||
foo:
|
||||
jsonSchema: |
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
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.
|
||||
|
||||
## Annotations
|
||||
To exempt a controller from all checks via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true
|
||||
```
|
||||
|
||||
To exempt a controller from a particular check via annotations, use an annotation in the form of `polaris.fairwinds.com/<check>-exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/cpuRequestsMissing-exempt=true
|
||||
```
|
||||
|
||||
## 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:
|
||||
```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
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- dns-controller
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
# exemption valid for hostNetworkSet rule on all containers in dns-controller controller 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
|
||||
```
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
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
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
|
||||
The Polaris dashboard can be installed on a cluster using kubectl or Helm. It
|
||||
can also be run locally, connecting to your cluster using the credentials stored in your `KUBECONFIG`.
|
||||
|
||||
The dashboard is a good way to understand what workloads inside your cluster or Infrastructure as Code
|
||||
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
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
|
||||
### Local Binary
|
||||
You'll need a valid `KUBECONFIG` set up for the dashboard to connect to your cluster.
|
||||
|
||||
Binary releases can be dowloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap reactiveops/tap
|
||||
brew install reactiveops/tap/polaris
|
||||
polaris dashboard --port 8080
|
||||
```
|
||||
|
||||
You can also point the dashboard to the local filesystem, instead of a live cluster:
|
||||
```bash
|
||||
polaris dashboard --port 8080 --audit-path=./deploy/
|
||||
```
|
||||
|
||||
### Local Docker container
|
||||
|
||||
```
|
||||
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro quay.io/fairwinds/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
|
||||
```
|
||||
|
||||
## Using the Dashboard
|
||||
The Polaris dashboard is a way to get a simple visual overview of the current state of your Kubernetes workloads as well as a roadmap for what can be improved. The dashboard provides a cluster wide overview as well as breaking out results by category, namespace, and workload.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
|
||||
</p>
|
||||
|
||||
Our default standards in Polaris are rather high, so don’t be surprised if your score is lower than you might expect. A key goal for Polaris was to set a high standard and aim for great configuration by default. If the defaults we’ve included are too strict, it’s easy to adjust the configuration as part of the deployment configuration to better suit your workloads.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Polaris Exit Codes for Audit Runs
|
||||
<dl>
|
||||
<dt>Exit 0</dt>
|
||||
<dd>Successful exit code</dd>
|
||||
<dt>Exit 1</dt>
|
||||
<dd>Could not run audit, or application had a failure while running.</dd>
|
||||
<dt>Exit 2</dt>
|
||||
<dd>Unused</dd>
|
||||
<dt>Exit 3</dt>
|
||||
<dd>Exiting due to `--set-exit-code-on-danger` being set and at least one danger was found after an audit.</dd>
|
||||
<dt>Edit 4</dd>
|
||||
<dd>Exiting due to `--set-exit-code-below-score` being set and the audit resulted in a score less than the minimum score value.</dd>
|
||||
</dl>
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
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)
|
||||
|
||||
Polaris can be used on the command line to audit local Kubernetes manifests stored in YAML files.
|
||||
This is particularly helpful for running Polaris against your infrastructure-as-code as part of a
|
||||
CI/CD pipeline. Use the available [command line flags](#running-in-a-ci-pipeline)
|
||||
to cause CI/CD to fail if your Polaris score drops below a certain threshold, or if any danger-level issues arise.
|
||||
|
||||
|
||||
## Install the CLI
|
||||
To run Polaris against your YAML manifests, e.g. as part of a Continuous Integration process,
|
||||
you'll need to install the CLI.
|
||||
|
||||
Binary releases can be downloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap FairwindsOps/tap
|
||||
brew install FairwindsOps/tap/polaris
|
||||
polaris version
|
||||
```
|
||||
|
||||
## 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.
|
||||
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--set-exit-code-on-danger \
|
||||
--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
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation"
|
||||
---
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"author": "",
|
||||
"bugs": {
|
||||
"url": "https://github.com/FairwindsOps/insights-docs/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"vuepress-plugin-check-md": "0.0.2"
|
||||
},
|
||||
"description": "A repository with a Vuepress template for Fairwinds projects",
|
||||
"devDependencies": {
|
||||
"vuepress": "^1.4.0",
|
||||
"vuepress-plugin-clean-urls": "^1.1.1",
|
||||
"vuepress-plugin-redirect": "^1.2.3"
|
||||
},
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
"homepage": "https://github.com/FairwindsOps/insights-docs#readme",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"name": "fairwinds-docs-template",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/FairwindsOps/insights-docs.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:readme && npm run build:docs",
|
||||
"build:docs": "vuepress build -d ../dist/",
|
||||
"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"
|
||||
},
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
# Installation and Usage
|
||||
Polaris can be installed on your cluster using kubectl or Helm. It can also
|
||||
be run as a local binary, which will use your kubeconfig to connect to the cluster
|
||||
or run against local YAML files.
|
||||
|
||||
## Configuration
|
||||
Polaris supports a wide range of validations covering a number of Kubernetes best practices.
|
||||
Here's a [sample configuration file](/examples/config-full.yaml) that includes all currently supported checks.
|
||||
The [default configuration](/examples/config.yaml) contains a number of those checks.
|
||||
|
||||
|
||||
### Checks
|
||||
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.
|
||||
|
||||
Polaris validation checks fall into several different categories:
|
||||
|
||||
- [Health Checks](check-documentation/health-checks.md)
|
||||
- [Images](check-documentation/images.md)
|
||||
- [Networking](check-documentation/networking.md)
|
||||
- [Resources](check-documentation/resources.md)
|
||||
- [Security](check-documentation/security.md)
|
||||
|
||||
#### Custom Checks
|
||||
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: Images
|
||||
target: Container # target can be "Container" or "Pod"
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
type: string
|
||||
not:
|
||||
pattern: ^quay.io
|
||||
```
|
||||
|
||||
Schemas can also be specified as JSON strings instead of YAML, for easier copy/pasting:
|
||||
```yaml
|
||||
customChecks:
|
||||
foo:
|
||||
jsonSchema: |
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
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](/examples/config-full.yaml)
|
||||
|
||||
There are additional examples in the [checks folder](/checks).
|
||||
|
||||
### 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 two ways: by annotating a controller, or editing the Polaris config.
|
||||
|
||||
To exempt a controller from all checks via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true
|
||||
```
|
||||
|
||||
To exempt a controller via the config, you have to specify a list of controller names and a list of rules, e.g.
|
||||
```yaml
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- dns-controller
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
```
|
||||
|
||||
To exempt a controller from a particular check via annotations, use an annotation in the form of `polaris.fairwinds.com/<check>-exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/cpuRequestsMissing-exempt=true
|
||||
```
|
||||
|
||||
# Installing
|
||||
There are several ways to install and use Polaris. Below outline ways to install using `kubectl`, `helm` and `local binary`.
|
||||
|
||||
## Dashboard
|
||||
The dashboard can be installed on a cluster using kubectl or Helm. It can also be run locally,
|
||||
connecting to your cluster using the credentials stored in your `KUBECONFIG`.
|
||||
|
||||
### 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
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
|
||||
### Local Binary
|
||||
You'll need a valid `KUBECONFIG` set up for the dashboard to connect to your cluster.
|
||||
|
||||
Binary releases can be dowloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap reactiveops/tap
|
||||
brew install reactiveops/tap/polaris
|
||||
polaris dashboard --port 8080
|
||||
```
|
||||
|
||||
You can also point the dashboard to the local filesystem, instead of a live cluster:
|
||||
```bash
|
||||
polaris dashboard --port 8080 --audit-path=./deploy/
|
||||
```
|
||||
|
||||
### Local Docker container
|
||||
|
||||
```
|
||||
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro quay.io/fairwinds/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
|
||||
```
|
||||
|
||||
## Webhook
|
||||
### kubectl
|
||||
```bash
|
||||
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/webhook.yaml
|
||||
```
|
||||
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwindsops-stable https://charts.fairwindsops.com/stable
|
||||
helm upgrade --install polaris fairwindsops-stable/polaris --namespace polaris \
|
||||
--set webhook.enable=true --set dashboard.enable=false
|
||||
```
|
||||
|
||||
## CLI
|
||||
### Installation
|
||||
Binary releases can be downloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap FairwindsOps/tap
|
||||
brew install FairwindsOps/tap/polaris
|
||||
polaris version
|
||||
```
|
||||
|
||||
You can run audits on the command line and see the output as JSON, YAML, or a raw score:
|
||||
```bash
|
||||
polaris audit --format yaml > report.yaml
|
||||
polaris audit --format score
|
||||
# 92
|
||||
```
|
||||
|
||||
Audits can run against a local directory or YAML file rather than a cluster:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/
|
||||
|
||||
# or to use STDIN
|
||||
cat pod.yaml | polaris audit --audit-path -
|
||||
```
|
||||
|
||||
You can also run the audit on a single resource instead of the entire cluster:
|
||||
```bash
|
||||
polaris audit --resource "nginx-ingress/Deployment.apps/v1/default-backend"
|
||||
```
|
||||
|
||||
#### Running with CI/CD
|
||||
You can integrate Polaris into CI/CD for repositories containing infrastructure-as-code.
|
||||
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--set-exit-code-on-danger \
|
||||
--set-exit-code-below-score 90
|
||||
```
|
||||
|
||||
For more on exit code meanings, see [exit-code docs](exit-codes.md).
|
||||
|
||||
#### 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)
|
||||
```
|
||||
@@ -1,30 +1,27 @@
|
||||
checks:
|
||||
# reliability
|
||||
multipleReplicasForDeployment: warning
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: warning
|
||||
# resources
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
# efficiency
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
# images
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
# healthChecks
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
# networking
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
# security
|
||||
hostIPCSet: danger
|
||||
hostPIDSet: danger
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
runAsRootAllowed: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
# custom
|
||||
resourceLimits: warning
|
||||
imageRegistry: danger
|
||||
@@ -35,6 +32,12 @@ exemptions:
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
- hostPortSet
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- my-network-controller
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
- hostPortSet
|
||||
|
||||
customChecks:
|
||||
resourceLimits:
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
checks:
|
||||
# reliability
|
||||
multipleReplicasForDeployment: ignore
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: ignore
|
||||
# resources
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
metadataAndNameMismatched: ignore
|
||||
pdbDisruptionsIsZero: warning
|
||||
missingPodDisruptionBudget: ignore
|
||||
|
||||
# efficiency
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
# images
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
# healthChecks
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
# networking
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
# security
|
||||
hostIPCSet: danger
|
||||
hostPIDSet: danger
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
runAsRootAllowed: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
tlsSettingsMissing: warning
|
||||
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- kube-apiserver
|
||||
- kube-proxy
|
||||
- kube-scheduler
|
||||
|
||||
@@ -7,24 +7,19 @@
|
||||
"ClusterInfo": {
|
||||
"Version": "unknown",
|
||||
"Nodes": 0,
|
||||
"Pods": 0,
|
||||
"Namespaces": 2,
|
||||
"Deployments": 2,
|
||||
"StatefulSets": 0,
|
||||
"DaemonSets": 0,
|
||||
"Jobs": 0,
|
||||
"CronJobs": 0,
|
||||
"ReplicationControllers": 0
|
||||
"Pods": 5,
|
||||
"Namespaces": 3,
|
||||
"Controllers": 5
|
||||
},
|
||||
"Results": [
|
||||
{
|
||||
"Name": "polaris-dashboard",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Deployment",
|
||||
"Messages": {},
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
@@ -50,7 +45,7 @@
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "dashboard",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
@@ -145,16 +140,17 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-webhook",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Deployment",
|
||||
"Messages": {},
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
@@ -180,7 +176,7 @@
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
@@ -275,7 +271,373 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-certificate-updater",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Job",
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostNetworkSet": {
|
||||
"ID": "hostNetworkSet",
|
||||
"Message": "Host network is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"hostPIDSet": {
|
||||
"ID": "hostPIDSet",
|
||||
"Message": "Host PID is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
}
|
||||
},
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook-certificate-generator",
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"cpuRequestsMissing": {
|
||||
"ID": "cpuRequestsMissing",
|
||||
"Message": "CPU requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"dangerousCapabilities": {
|
||||
"ID": "dangerousCapabilities",
|
||||
"Message": "Container does not have any dangerous capabilities",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostPortSet": {
|
||||
"ID": "hostPortSet",
|
||||
"Message": "Host port is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"insecureCapabilities": {
|
||||
"ID": "insecureCapabilities",
|
||||
"Message": "Container does not have any insecure capabilities",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"memoryLimitsMissing": {
|
||||
"ID": "memoryLimitsMissing",
|
||||
"Message": "Memory limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"memoryRequestsMissing": {
|
||||
"ID": "memoryRequestsMissing",
|
||||
"Message": "Memory requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"privilegeEscalationAllowed": {
|
||||
"ID": "privilegeEscalationAllowed",
|
||||
"Message": "Privilege escalation not allowed",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsPrivileged": {
|
||||
"ID": "runAsPrivileged",
|
||||
"Message": "Not running as privileged",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsRootAllowed": {
|
||||
"ID": "runAsRootAllowed",
|
||||
"Message": "Is not allowed to run as root",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"tagNotSpecified": {
|
||||
"ID": "tagNotSpecified",
|
||||
"Message": "Image tag is specified",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Images"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-webhook",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Deployment",
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostNetworkSet": {
|
||||
"ID": "hostNetworkSet",
|
||||
"Message": "Host network is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"hostPIDSet": {
|
||||
"ID": "hostPIDSet",
|
||||
"Message": "Host PID is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
}
|
||||
},
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook",
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"cpuRequestsMissing": {
|
||||
"ID": "cpuRequestsMissing",
|
||||
"Message": "CPU requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"dangerousCapabilities": {
|
||||
"ID": "dangerousCapabilities",
|
||||
"Message": "Container does not have any dangerous capabilities",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostPortSet": {
|
||||
"ID": "hostPortSet",
|
||||
"Message": "Host port is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"insecureCapabilities": {
|
||||
"ID": "insecureCapabilities",
|
||||
"Message": "Container does not have any insecure capabilities",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"livenessProbeMissing": {
|
||||
"ID": "livenessProbeMissing",
|
||||
"Message": "Liveness probe is configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Health Checks"
|
||||
},
|
||||
"memoryLimitsMissing": {
|
||||
"ID": "memoryLimitsMissing",
|
||||
"Message": "Memory limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"memoryRequestsMissing": {
|
||||
"ID": "memoryRequestsMissing",
|
||||
"Message": "Memory requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"privilegeEscalationAllowed": {
|
||||
"ID": "privilegeEscalationAllowed",
|
||||
"Message": "Privilege escalation not allowed",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"readinessProbeMissing": {
|
||||
"ID": "readinessProbeMissing",
|
||||
"Message": "Readiness probe is configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Health Checks"
|
||||
},
|
||||
"runAsPrivileged": {
|
||||
"ID": "runAsPrivileged",
|
||||
"Message": "Not running as privileged",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsRootAllowed": {
|
||||
"ID": "runAsRootAllowed",
|
||||
"Message": "Is not allowed to run as root",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"tagNotSpecified": {
|
||||
"ID": "tagNotSpecified",
|
||||
"Message": "Image tag is specified",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Images"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-certificate-updater",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Job",
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostNetworkSet": {
|
||||
"ID": "hostNetworkSet",
|
||||
"Message": "Host network is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"hostPIDSet": {
|
||||
"ID": "hostPIDSet",
|
||||
"Message": "Host PID is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
}
|
||||
},
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook-certificate-generator",
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"cpuRequestsMissing": {
|
||||
"ID": "cpuRequestsMissing",
|
||||
"Message": "CPU requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"dangerousCapabilities": {
|
||||
"ID": "dangerousCapabilities",
|
||||
"Message": "Container does not have any dangerous capabilities",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostPortSet": {
|
||||
"ID": "hostPortSet",
|
||||
"Message": "Host port is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"insecureCapabilities": {
|
||||
"ID": "insecureCapabilities",
|
||||
"Message": "Container does not have any insecure capabilities",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"memoryLimitsMissing": {
|
||||
"ID": "memoryLimitsMissing",
|
||||
"Message": "Memory limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"memoryRequestsMissing": {
|
||||
"ID": "memoryRequestsMissing",
|
||||
"Message": "Memory requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"privilegeEscalationAllowed": {
|
||||
"ID": "privilegeEscalationAllowed",
|
||||
"Message": "Privilege escalation not allowed",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsPrivileged": {
|
||||
"ID": "runAsPrivileged",
|
||||
"Message": "Not running as privileged",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsRootAllowed": {
|
||||
"ID": "runAsRootAllowed",
|
||||
"Message": "Is not allowed to run as root",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"tagNotSpecified": {
|
||||
"ID": "tagNotSpecified",
|
||||
"Message": "Image tag is specified",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Images"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
options:
|
||||
organization: fairwinds-opensource
|
||||
baseBranch: master
|
||||
|
||||
# These images will be scanned for vulnerabilities.
|
||||
images:
|
||||
docker:
|
||||
- quay.io/fairwinds/polaris:$CI_SHA1
|
||||
|
||||
# These manifests will be scanned for configuration issues.
|
||||
manifests:
|
||||
yaml:
|
||||
- ./deploy/dashboard.yaml
|
||||
- ./deploy/webhook.yaml
|
||||
@@ -1,40 +1,25 @@
|
||||
module github.com/fairwindsops/polaris
|
||||
|
||||
go 1.13
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
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.0
|
||||
github.com/google/go-cmp v0.5.2 // indirect
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/gobuffalo/packr/v2 v2.8.1
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/gophercloud/gophercloud v0.12.0 // indirect
|
||||
github.com/google/uuid v1.1.3 // 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
|
||||
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.0.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.2.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.3
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/thoas/go-funk v0.9.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
sigs.k8s.io/controller-runtime v0.10.1
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/cmd/polaris"
|
||||
cmd "github.com/fairwindsops/polaris/cmd/polaris"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "1.2.1"
|
||||
Version = "5.0.0"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
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",
|
||||
// Container checks
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
"cpuLimitsMissing",
|
||||
"cpuRequestsMissing",
|
||||
"readinessProbeMissing",
|
||||
"livenessProbeMissing",
|
||||
"pullPolicyNotAlways",
|
||||
"tagNotSpecified",
|
||||
"hostPortSet",
|
||||
"runAsRootAllowed",
|
||||
"runAsPrivileged",
|
||||
"notReadOnlyRootFilesystem",
|
||||
"privilegeEscalationAllowed",
|
||||
"dangerousCapabilities",
|
||||
"insecureCapabilities",
|
||||
"priorityClassNotSet",
|
||||
// Other checks
|
||||
"tlsSettingsMissing",
|
||||
"pdbDisruptionsIsZero",
|
||||
"metadataAndNameMismatched",
|
||||
"missingPodDisruptionBudget",
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -23,23 +23,27 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
packr "github.com/gobuffalo/packr/v2"
|
||||
"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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
var configBox = (*packr.Box)(nil)
|
||||
@@ -58,14 +62,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 {
|
||||
@@ -101,8 +105,8 @@ func Parse(rawBytes []byte) (Configuration, error) {
|
||||
}
|
||||
|
||||
// Validate checks if a config is valid
|
||||
func (c Configuration) Validate() error {
|
||||
if len(c.Checks) == 0 {
|
||||
func (conf Configuration) Validate() error {
|
||||
if len(conf.Checks) == 0 {
|
||||
return errors.New("No checks were enabled")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -16,6 +16,7 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -142,7 +143,9 @@ func TestConfigNoServerError(t *testing.T) {
|
||||
|
||||
func TestConfigWithCustomChecks(t *testing.T) {
|
||||
valid := map[string]interface{}{
|
||||
"securityContext": map[string]interface{}{},
|
||||
"securityContext": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
invalid := map[string]interface{}{
|
||||
"notSecurityContext": map[string]interface{}{},
|
||||
@@ -151,20 +154,23 @@ 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))
|
||||
isValid, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]interface{}{})
|
||||
isValid, _, err := check.CheckObject(valid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
|
||||
isValid, _, err = check.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, err = parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
|
||||
if !assert.Equal(t, true, isValid) {
|
||||
fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
|
||||
}
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
}
|
||||
|
||||
@@ -2,35 +2,53 @@ 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, controllerName string) bool {
|
||||
func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, containerName string) bool {
|
||||
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
|
||||
return false
|
||||
}
|
||||
if conf.DisallowExemptions {
|
||||
if conf.DisallowExemptions || conf.DisallowConfigExemptions {
|
||||
return true
|
||||
}
|
||||
for _, exemption := range conf.Exemptions {
|
||||
if exemption.Namespace != "" && exemption.Namespace != objMeta.GetNamespace() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, example := range conf.Exemptions {
|
||||
for _, rule := range example.Rules {
|
||||
checkIfRuleMatches := false
|
||||
for _, rule := range exemption.Rules {
|
||||
if rule != ruleID {
|
||||
continue
|
||||
}
|
||||
for _, controller := range example.ControllerNames {
|
||||
if strings.HasPrefix(controllerName, controller) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
checkIfRuleMatches = true
|
||||
break
|
||||
}
|
||||
if len(example.Rules) == 0 {
|
||||
for _, controller := range example.ControllerNames {
|
||||
if strings.HasPrefix(controllerName, controller) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(exemption.Rules) == 0 || checkIfRuleMatches {
|
||||
if !isExemptionCheckMatched(exemption.ControllerNames, objMeta.GetName()) {
|
||||
continue
|
||||
}
|
||||
if isExemptionCheckMatched(exemption.ContainerNames, containerName) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -18,45 +18,248 @@ 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 confExemptRuleTest = `
|
||||
var confContainerTest = `
|
||||
checks:
|
||||
ANY: warning
|
||||
OTHER: warning
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: warning
|
||||
pullPolicyNotAlways: warning
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- test
|
||||
- namespace: prometheus
|
||||
rules:
|
||||
- ANY
|
||||
`
|
||||
|
||||
var confExemptTest = `
|
||||
checks:
|
||||
ANY: warning
|
||||
exemptions:
|
||||
- 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:
|
||||
- test
|
||||
- controller6
|
||||
containerNames:
|
||||
- container61
|
||||
- container62
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- controller7
|
||||
containerNames:
|
||||
- container71
|
||||
- container72
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- priorityClassNotSet
|
||||
- namespace: polaris
|
||||
`
|
||||
|
||||
func TestInclusiveExemption(t *testing.T) {
|
||||
parsedConf, _ := Parse([]byte(confExemptTest))
|
||||
applicable := parsedConf.IsActionable("ANY", "test")
|
||||
applicableOtherController := parsedConf.IsActionable("ANY", "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 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
|
||||
}
|
||||
|
||||
func TestIndividualRuleException(t *testing.T) {
|
||||
parsedConf, _ := Parse([]byte(confExemptRuleTest))
|
||||
applicable := parsedConf.IsActionable("ANY", "test")
|
||||
applicableOtherRule := parsedConf.IsActionable("OTHER", "test")
|
||||
applicableOtherRuleOtherController := parsedConf.IsActionable("OTHER", "other")
|
||||
applicableRuleOtherController := parsedConf.IsActionable("ANY", "other")
|
||||
func TestNamespaceExemptionForSpecifiedRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
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.")
|
||||
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)
|
||||
}
|
||||
|
||||
func TestNamespaceExemptionForAllRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,43 +1,86 @@
|
||||
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"
|
||||
// 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,
|
||||
TargetPod,
|
||||
}
|
||||
|
||||
// SchemaCheck is a Polaris check that runs using JSON Schema
|
||||
type SchemaCheck struct {
|
||||
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"`
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
type resourceMinimum string
|
||||
type resourceMaximum string
|
||||
|
||||
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)
|
||||
@@ -117,48 +160,136 @@ 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.JSONSchema != "" {
|
||||
if err := json.Unmarshal([]byte(check.JSONSchema), &check.Schema); err != nil {
|
||||
if check.SchemaString == "" {
|
||||
jsonBytes, err := json.Marshal(check.Schema)
|
||||
if 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
|
||||
}
|
||||
|
||||
// CheckPod checks a pod spec against the schema
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(pod)
|
||||
}
|
||||
|
||||
// CheckController checks a controler's spec against the schema
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, error) {
|
||||
errs, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errs) == 0, err
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
}
|
||||
|
||||
// CheckContainer checks a container spec against the schema
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(container)
|
||||
}
|
||||
|
||||
// CheckObject checks arbitrary data against the schema
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, nil, err
|
||||
}
|
||||
errs, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errs) == 0, 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
|
||||
}
|
||||
|
||||
// IsActionable decides if this check applies to a particular target
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool {
|
||||
if check.Target != target {
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
|
||||
if funk.Contains(HandledTargets, target) {
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
} else if string(check.Target) != kind && !strings.HasSuffix(string(check.Target), "/"+kind) {
|
||||
return false
|
||||
}
|
||||
isIncluded := len(check.Controllers.Include) == 0
|
||||
for _, inclusion := range check.Controllers.Include {
|
||||
if inclusion == controllerType {
|
||||
if inclusion == kind {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
@@ -167,7 +298,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, controllerType string,
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Controllers.Exclude {
|
||||
if exclusion == controllerType {
|
||||
if exclusion == kind {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,27 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card.insights {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #20162D;
|
||||
background-color: #CACBD2;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights a {
|
||||
background-color: #20162D;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
.filters .title,
|
||||
.card h3 {
|
||||
margin: 0;
|
||||
@@ -359,4 +380,4 @@ form.namespace-list {
|
||||
.namespace-row label {
|
||||
margin-left: 4px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer .row {
|
||||
margin: 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #200C36;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.footer > div {
|
||||
width: 200px;
|
||||
}
|
||||