mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6c6bbe893 | ||
|
|
8e7100acaf | ||
|
|
42d2b3368b | ||
|
|
4d96993a18 | ||
|
|
1486e3090f | ||
|
|
a2eaa210f6 | ||
|
|
e8e642b010 | ||
|
|
1b50b85157 | ||
|
|
5f7bbd981b | ||
|
|
3cf4a88b93 | ||
|
|
5a2a72b582 | ||
|
|
01dd7b7b68 | ||
|
|
6b64369698 | ||
|
|
9448686168 | ||
|
|
01d7a8ac00 | ||
|
|
742b21c6a2 | ||
|
|
b90f091bb6 | ||
|
|
e3a6cb3774 | ||
|
|
7addced32c | ||
|
|
7e77350428 | ||
|
|
af0d548a07 | ||
|
|
3efa3b40c9 | ||
|
|
206322271c | ||
|
|
e5b9236268 | ||
|
|
c3b57bf6c7 | ||
|
|
652b65b3c2 | ||
|
|
41030320bb | ||
|
|
76c42c4799 | ||
|
|
65add73e70 | ||
|
|
a0000e1919 | ||
|
|
f9e2603b16 | ||
|
|
50d789fd42 | ||
|
|
25ab600eef | ||
|
|
be45519a22 | ||
|
|
ccaa384cd0 | ||
|
|
1c09ce9e09 | ||
|
|
fec00893b1 | ||
|
|
acadebe9fd | ||
|
|
a2ec025230 | ||
|
|
08682075c6 | ||
|
|
e3e790046f | ||
|
|
50319fb1b8 | ||
|
|
c3eb0811e0 | ||
|
|
5423449177 | ||
|
|
f713d43697 | ||
|
|
e896eec89f | ||
|
|
25a120ba65 | ||
|
|
3b865fcea8 | ||
|
|
f71ca999c9 | ||
|
|
276c168839 | ||
|
|
92f0b6e551 | ||
|
|
6b7d6ab301 | ||
|
|
6c33168378 | ||
|
|
8a8ac2d9b9 | ||
|
|
a59063bdb2 | ||
|
|
c597b162d9 | ||
|
|
322e6f7dcd | ||
|
|
c92819ca9d | ||
|
|
57d0ae3932 | ||
|
|
321bfa8f1f | ||
|
|
78838a606d | ||
|
|
fd16fb993d | ||
|
|
1841b7441d | ||
|
|
ce8786b9d2 | ||
|
|
82d366364f | ||
|
|
bd8b2962dc | ||
|
|
f2833f2c4e | ||
|
|
a4c0b0f555 | ||
|
|
a7e30075a9 | ||
|
|
861fd42d29 | ||
|
|
ef50fbbff6 | ||
|
|
f429f1922a | ||
|
|
06322dc8a4 | ||
|
|
afe718b621 | ||
|
|
5e4d6bd857 | ||
|
|
e61c50a6a1 | ||
|
|
b1e22ab47b | ||
|
|
986d47b5dd | ||
|
|
8c18e40024 | ||
|
|
f79d0ee3ee | ||
|
|
539fa088d1 | ||
|
|
ab7bb94b39 | ||
|
|
2aedaa9ba4 | ||
|
|
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 |
+43
-82
@@ -1,5 +1,9 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
rok8s: fairwinds/rok8s-scripts@11
|
||||
oss-docs: fairwinds/oss-docs@0
|
||||
|
||||
executors:
|
||||
vm:
|
||||
machine:
|
||||
@@ -18,6 +22,7 @@ references:
|
||||
echo 'export GOPROXY=https://proxy.golang.org' >> ${BASH_ENV}
|
||||
echo 'export GO111MODULE=on' >> ${BASH_ENV}
|
||||
echo 'export GOFLAGS=-mod=mod' >> ${BASH_ENV}
|
||||
echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
|
||||
|
||||
install_k8s: &install_k8s
|
||||
run:
|
||||
@@ -28,20 +33,20 @@ references:
|
||||
sudo apt-get install -yqq jq git
|
||||
|
||||
echo "Installing KIND"
|
||||
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.8.1/kind-linux-amd64
|
||||
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.14.0/kind-linux-amd64
|
||||
chmod 0755 kind-linux-amd64
|
||||
sudo mv kind-linux-amd64 /usr/local/bin/kind
|
||||
kind version
|
||||
|
||||
echo "Installing Kubectl"
|
||||
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.18.6/bin/linux/amd64/kubectl
|
||||
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.21.12/bin/linux/amd64/kubectl
|
||||
chmod 0755 kubectl
|
||||
sudo mv kubectl /usr/local/bin/
|
||||
kubectl version --client
|
||||
|
||||
|
||||
echo "Creating Kubernetes Cluster with Kind"
|
||||
kind create cluster --wait=90s --image kindest/node:v1.15.11
|
||||
kind create cluster --wait=90s --image kindest/node:v1.21.12
|
||||
docker ps -a
|
||||
|
||||
kubectl version
|
||||
@@ -82,23 +87,6 @@ references:
|
||||
echo "Skipping Kubernetes tests for forked PR"
|
||||
fi
|
||||
|
||||
# Release scripts
|
||||
install_goreleaser: &install_goreleaser
|
||||
run:
|
||||
name: Install GoReleaser
|
||||
command: |
|
||||
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.131.1/goreleaser_amd64.deb
|
||||
echo "640790dcbfa864f26de4c26c2d491f293a64525c8c6641c5bbdec7136b38977e goreleaser.deb" | sha256sum -c -
|
||||
sudo dpkg -i goreleaser.deb
|
||||
rm goreleaser.deb
|
||||
|
||||
docker_build: &docker_build
|
||||
run:
|
||||
name: Docker login and build
|
||||
command: |
|
||||
docker-pull -f .circleci/build.config
|
||||
docker-build -f .circleci/build.config
|
||||
|
||||
docker_build_and_push: &docker_build_and_push
|
||||
run:
|
||||
name: Docker login, build, and push
|
||||
@@ -108,25 +96,27 @@ references:
|
||||
docker login quay.io -u="${fairwinds_quay_user}" -p="${fairwinds_quay_token}"
|
||||
docker-push -f .circleci/build.config
|
||||
|
||||
release_deploy_configs: &release_deploy_configs
|
||||
enable_experimental_features: &enable_experimental_docker_features
|
||||
run:
|
||||
name: Release deploy configs
|
||||
name: enable experimental features
|
||||
command: |
|
||||
upload_url=$(curl --silent https://api.github.com/repos/FairwindsOps/polaris/releases/latest | grep upload_url)
|
||||
upload_url=$(echo $upload_url | sed -e 's/.*\(https.*\){.*$/\1/')
|
||||
curl -X POST "$upload_url?name=dashboard.yaml" --data-binary "@./deploy/dashboard.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
|
||||
curl -X POST "$upload_url?name=webhook.yaml" --data-binary "@./deploy/webhook.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
|
||||
set -ex
|
||||
apk --update add openssh
|
||||
ssh remote-docker \<<EOF
|
||||
sudo bash -c 'echo "{\"experimental\": true}" > /etc/docker/daemon.json'
|
||||
sudo systemctl restart docker
|
||||
EOF
|
||||
install_vault_alpine: &install_vault_alpine
|
||||
run:
|
||||
name: install hashicorp vault
|
||||
command: |
|
||||
apk --update add curl yq
|
||||
cd /tmp
|
||||
curl -LO https://releases.hashicorp.com/vault/1.9.3/vault_1.9.3_linux_amd64.zip
|
||||
unzip vault_1.9.3_linux_amd64.zip
|
||||
mv vault /usr/bin/vault
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: quay.io/reactiveops/ci-images:v11.0-stretch
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- *set_environment_variables
|
||||
- *docker_build
|
||||
|
||||
push:
|
||||
docker:
|
||||
- image: quay.io/reactiveops/ci-images:v11.0-stretch
|
||||
@@ -146,17 +136,13 @@ jobs:
|
||||
- *test_k8s
|
||||
|
||||
test:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
docker:
|
||||
- image: circleci/golang:1.16
|
||||
- image: cimg/go:1.19
|
||||
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 vet ./...
|
||||
- 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
|
||||
- *test_binary_dashboard
|
||||
|
||||
insights:
|
||||
@@ -165,29 +151,32 @@ jobs:
|
||||
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/
|
||||
resource_class: large
|
||||
shell: /bin/bash
|
||||
docker:
|
||||
- image: circleci/golang:1.16
|
||||
- image: goreleaser/goreleaser:v1.3.0
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- setup_remote_docker:
|
||||
version: 20.10.6
|
||||
- *enable_experimental_docker_features
|
||||
- *install_vault_alpine
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/global/env
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/polaris/env
|
||||
- *set_environment_variables
|
||||
- *install_goreleaser
|
||||
- run: go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
- run: git checkout -- . # FIXME: the go get makes the directory dirty
|
||||
- run: packr2
|
||||
- run: echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
|
||||
- run: goreleaser
|
||||
- *release_deploy_configs
|
||||
|
||||
release_images:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
@@ -198,45 +187,16 @@ jobs:
|
||||
- setup_remote_docker
|
||||
- *set_environment_variables
|
||||
- *docker_build_and_push
|
||||
|
||||
publish_docs:
|
||||
docker:
|
||||
- image: cimg/node:15.5.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Build Docs Site
|
||||
command: |
|
||||
set -e
|
||||
cd ./docs
|
||||
npm install
|
||||
npm run check-links
|
||||
npm run build
|
||||
- run:
|
||||
name: Install AWS CLI
|
||||
command: |
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
- run:
|
||||
name: Publish Docs Site to S3
|
||||
command: |
|
||||
cd ./dist
|
||||
aws s3 sync ./ s3://polaris.docs.fairwinds.com --delete
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
build:
|
||||
test_and_push:
|
||||
jobs:
|
||||
- test
|
||||
- build:
|
||||
requires:
|
||||
- test
|
||||
- push:
|
||||
context: org-global
|
||||
requires:
|
||||
- build
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
@@ -271,7 +231,8 @@ workflows:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- publish_docs:
|
||||
- oss-docs/publish-docs:
|
||||
repository: polaris
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
||||
@@ -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. If a helm chart, please share the name of the chart.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: search
|
||||
attributes:
|
||||
label: Search
|
||||
options:
|
||||
- label: I did search for other open and closed issues before opening this.
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you agree to follow the CODE_OF_CONDUCT in this repository.
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
- type: textarea
|
||||
id: ctx
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Anything else you would like to add
|
||||
validations:
|
||||
required: false
|
||||
@@ -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
|
||||
|
||||
-->
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: [triage, enhancement]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
name: Other
|
||||
about: For misc. tasks like research or continued conversation
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: [triage]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -3,15 +3,16 @@ 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."
|
||||
echo "Installing polaris to path from " $POLARIS_URL
|
||||
fi
|
||||
TARGET_FILE="polaris.tar.gz"
|
||||
curl -LJ -o $TARGET_FILE 'https://github.com/FairwindsOps/polaris/releases/download/'"$INPUT_VERSION"'/polaris_'"$INPUT_VERSION"'_linux_386.tar.gz'
|
||||
curl -LJ -o $TARGET_FILE $POLARIS_URL
|
||||
mkdir polaris
|
||||
tar -xzf $TARGET_FILE -C polaris
|
||||
rm $TARGET_FILE
|
||||
|
||||
+18
-42
@@ -1,44 +1,20 @@
|
||||
## DO NOT EDIT - Managed by Terraform
|
||||
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
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/docs"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 0
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
This PR fixes #
|
||||
|
||||
## Checklist
|
||||
* [ ] I have signed the CLA
|
||||
* [ ] I have updated/added any relevant documentation
|
||||
|
||||
## Description
|
||||
### What's the goal of this PR?
|
||||
|
||||
### What changes did you make?
|
||||
|
||||
### What alternative solution should we consider, if any?
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
daysUntilStale: 30
|
||||
daysUntilClose: 7
|
||||
onlyLabels: []
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
|
||||
exemptProjects: false
|
||||
exemptMilestones: true
|
||||
exemptAssignees: false
|
||||
staleLabel: stale
|
||||
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
limitPerRun: 30
|
||||
@@ -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
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Setup polaris
|
||||
uses: ./.github/actions/setup-polaris
|
||||
with:
|
||||
version: 3.0.3
|
||||
version: 4.2.0
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
|
||||
@@ -22,6 +22,6 @@ jobs:
|
||||
- name: Setup polaris
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
with:
|
||||
version: 3.0.3
|
||||
version: 4.2.0
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Binaries for programs and plugins
|
||||
.go-version
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
|
||||
+24
-2
@@ -7,10 +7,16 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
builds:
|
||||
- id: watcher
|
||||
- id: polaris
|
||||
ldflags:
|
||||
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
@@ -18,9 +24,25 @@ builds:
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
archives:
|
||||
- id: polaris
|
||||
builds: ["polaris"]
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
||||
signs:
|
||||
- cmd: cosign
|
||||
args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}"]
|
||||
artifacts: checksum
|
||||
release:
|
||||
prerelease: auto
|
||||
footer: |
|
||||
You can verify the signature of the checksums.txt file using [cosign](https://github.com/sigstore/cosign).
|
||||
|
||||
```
|
||||
cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub
|
||||
```
|
||||
brews:
|
||||
- name: polaris
|
||||
github:
|
||||
tap:
|
||||
owner: FairwindsOps
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
header:
|
||||
license:
|
||||
spdx-id: Apache-2.0
|
||||
copyright-owner: 'FairwindsOps, Inc.'
|
||||
content: |
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
paths:
|
||||
- '**/*.go'
|
||||
comment: on-failure
|
||||
dependency:
|
||||
files:
|
||||
- go.mod
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
* @rbren @makoscafee @baderbuddy
|
||||
## DO NOT EDIT - Managed by Terraform
|
||||
* @rbren @makoscafee
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource@fairwinds.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.16 AS build-env
|
||||
FROM golang:1.19 AS build-env
|
||||
WORKDIR /go/src/github.com/fairwindsops/polaris/
|
||||
|
||||
ENV GO111MODULE=on
|
||||
@@ -10,13 +10,14 @@ ENV GOARCH=amd64
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
RUN go mod download
|
||||
RUN go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
RUN go install github.com/gobuffalo/packr/v2/packr2@latest
|
||||
|
||||
COPY . .
|
||||
RUN packr2 build -a -o polaris *.go
|
||||
|
||||
FROM alpine:3.13
|
||||
FROM alpine:3.16
|
||||
WORKDIR /usr/local/bin
|
||||
RUN apk -U upgrade
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
RUN addgroup -S polaris && adduser -u 1200 -S polaris -G polaris
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<div align="center" class="no-border">
|
||||
<img src="https://polaris.docs.fairwinds.com/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=4.0.2&color=239922">
|
||||
<h3>Polaris is an open source policy engine for Kubernetes</h3>
|
||||
<a href="https://github.com/FairwindsOps/polaris/releases">
|
||||
<img src="https://img.shields.io/github/v/release/FairwindsOps/polaris">
|
||||
</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">
|
||||
<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">
|
||||
@@ -16,15 +16,12 @@
|
||||
</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 is an open source policy engine for Kubernetes that validates and remediates resource configuration. It includes 30+ built in configuration policies, as well as the ability to build custom policies with JSON Schema. When run on the command line or as a mutating webhook, Polaris can automatically remediate issues based on policy criteria.
|
||||
|
||||
Polaris can be run in three different modes:
|
||||
* 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.
|
||||
|
||||
* As a [dashboard](https://polaris.docs.fairwinds.com/dashboard) - Validate Kubernetes resources against policy-as-code.
|
||||
* As an [admission controller](https://polaris.docs.fairwinds.com/admission-controller) - Automatically reject or modify workloads that don't adhere to your organization's policies.
|
||||
* As a [command-line tool](https://polaris.docs.fairwinds.com/infrastructure-as-code) - Incorporate policy-as-code into the CI/CD process to test local YAML files.
|
||||
<p align="center">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/architecture.svg" alt="Polaris Architecture" width="550"/>
|
||||
</p>
|
||||
@@ -32,10 +29,18 @@ Polaris can be run in three different modes:
|
||||
## Documentation
|
||||
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
|
||||
|
||||
<!-- Begin boilerplate -->
|
||||
## Join the Fairwinds Open Source Community
|
||||
|
||||
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!
|
||||
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!
|
||||
|
||||
<a href="https://www.fairwinds.com/t-shirt-offer?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris-tshirt">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png" alt="Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt!" />
|
||||
</a>
|
||||
|
||||
## Other Projects from Fairwinds
|
||||
|
||||
@@ -45,12 +50,14 @@ Enjoying Polaris? Check out some of our other projects:
|
||||
* [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
|
||||
|
||||
Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris)
|
||||
## Fairwinds Insights
|
||||
<p align="center">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/FW_Insights_Polaris.svg" alt="Fairwinds Insights" width="550"/>
|
||||
</p>
|
||||
|
||||
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.
|
||||
[Fairwinds Insights](https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris),
|
||||
a platform for auditing and enforcing policy in Kubernetes clusters.
|
||||
|
||||
<a href="https://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>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
successMessage: The ServiceAccount will not be automounted
|
||||
failureMessage: The ServiceAccount will be automounted
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required: ["serviceAccountName"]
|
||||
properties:
|
||||
serviceAccountName:
|
||||
type: string
|
||||
automountServiceAccountToken:
|
||||
type: boolean
|
||||
not:
|
||||
const: true
|
||||
additionalSchemaStrings:
|
||||
ServiceAccount: |
|
||||
type: object
|
||||
required:
|
||||
- metadata
|
||||
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
|
||||
- automountServiceAccountToken
|
||||
{{ end }}
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .Polaris.PodSpec.serviceAccountName }}"
|
||||
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
|
||||
automountServiceAccountToken:
|
||||
type: boolean
|
||||
const: false
|
||||
{{ end }}
|
||||
@@ -0,0 +1,55 @@
|
||||
successMessage: The ClusterRole does not allow pods/exec or pods/attach
|
||||
failureMessage: The ClusterRole allows Pods/exec or pods/attach
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRole
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
anyOf:
|
||||
# Do not alert on default ClusterRoles.
|
||||
- properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: 'admin'
|
||||
- const: "cluster-admin"
|
||||
- const: "edit"
|
||||
- const: "system:aggregate-to-edit"
|
||||
- const: "system:controller:generic-garbage-collector"
|
||||
- const: "system:controller:namespace-controller"
|
||||
- properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: ""
|
||||
- const: '*'
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
- const: "pods/exec"
|
||||
- const: "pods/attach"
|
||||
verbs:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
# An exec is also possible by `get`ing a web socket.
|
||||
- const: 'get'
|
||||
- const: 'create'
|
||||
@@ -0,0 +1,88 @@
|
||||
successMessage: The ClusterRoleBinding does not reference the default cluster-admin ClusterRole or one with wildcard permissions
|
||||
failureMessage: The ClusterRoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
- required: ["metadata"]
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: "cluster-admin"
|
||||
- const: "system:controller:generic-garbage-collector"
|
||||
- const: "system:controller:namespace-controller"
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["apiGroup", "kind", "name"]
|
||||
properties:
|
||||
apiGroup:
|
||||
type: string
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "ClusterRole"
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
not:
|
||||
const: "cluster-admin"
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
{{ if and (ne .metadata.name "cluster-admin") (ne .metadata.name "system:controller:generic-garbage-collector") (ne .metadata.name "system:controller:namespace-controller") }}
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .roleRef.name }}"
|
||||
- properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
const: "*"
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
const: '*'
|
||||
verbs:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
oneOf:
|
||||
- contains:
|
||||
type: string
|
||||
const: '*'
|
||||
- minItems: 7
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- "get"
|
||||
- "list"
|
||||
- "watch"
|
||||
- "create"
|
||||
- "update"
|
||||
- "patch"
|
||||
- "delete"
|
||||
{{ end }}
|
||||
@@ -0,0 +1,82 @@
|
||||
successMessage: The ClusterRoleBinding does not reference a ClusterRole allowing pods/exec or pods/attach
|
||||
failureMessage: The ClusterRoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
- required: ["metadata"]
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: "cluster-admin"
|
||||
- const: "system:controller:generic-garbage-collector"
|
||||
- const: "system:controller:namespace-controller"
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["apiGroup", "kind", "name"]
|
||||
properties:
|
||||
apiGroup:
|
||||
type: string
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "ClusterRole"
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
{{ if and (ne .metadata.name "cluster-admin") (ne .metadata.name "system:controller:generic-garbage-collector") (ne .metadata.name "system:controller:namespace-controller") }}
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .roleRef.name }}"
|
||||
- properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: ""
|
||||
- const: '*'
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
- const: "pods/exec"
|
||||
- const: "pods/attach"
|
||||
verbs:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
# An exec is also possible by `get`ing a web socket.
|
||||
- const: 'get'
|
||||
- const: 'create'
|
||||
{{ end }}
|
||||
@@ -25,3 +25,8 @@ schema:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/limits/cpu
|
||||
value: 100m
|
||||
comment: "TODO: Set this to the maximum amount of CPU you want your workload to use"
|
||||
|
||||
@@ -25,3 +25,8 @@ schema:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/requests/cpu
|
||||
value: 100m
|
||||
comment: "TODO: Set this to the amount of CPU you want to reserve for your workload"
|
||||
|
||||
@@ -17,10 +17,15 @@ schema:
|
||||
allOf:
|
||||
- not:
|
||||
contains:
|
||||
const: ALL
|
||||
pattern: '^(?i)ALL$'
|
||||
- not:
|
||||
contains:
|
||||
const: SYS_ADMIN
|
||||
pattern: '^(?i)SYS_ADMIN$'
|
||||
- not:
|
||||
contains:
|
||||
const: NET_ADMIN
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
|
||||
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /securityContext/capabilities
|
||||
|
||||
@@ -19,3 +19,7 @@ schema:
|
||||
replicas:
|
||||
type: integer
|
||||
minimum: 2
|
||||
mutations:
|
||||
- op: add
|
||||
path: /spec/replicas
|
||||
value: 3
|
||||
@@ -1,7 +1,7 @@
|
||||
successMessage: Host IPC is not configured
|
||||
failureMessage: Host IPC should not be configured
|
||||
category: Security
|
||||
target: Pod
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
@@ -9,3 +9,6 @@ schema:
|
||||
hostIPC:
|
||||
not:
|
||||
const: true
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /hostIPC
|
||||
@@ -1,7 +1,7 @@
|
||||
successMessage: Host network is not configured
|
||||
failureMessage: Host network should not be configured
|
||||
category: Security
|
||||
target: Pod
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
@@ -9,3 +9,6 @@ schema:
|
||||
hostNetwork:
|
||||
not:
|
||||
const: true
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /hostNetwork
|
||||
@@ -1,7 +1,7 @@
|
||||
successMessage: Host PID is not configured
|
||||
failureMessage: Host PID should not be configured
|
||||
category: Security
|
||||
target: Pod
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
@@ -9,3 +9,6 @@ schema:
|
||||
hostPID:
|
||||
not:
|
||||
const: true
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /hostPID
|
||||
@@ -13,3 +13,6 @@ schema:
|
||||
properties:
|
||||
hostPort:
|
||||
const: 0
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /ports/*/hostPort
|
||||
|
||||
@@ -22,35 +22,39 @@ schema:
|
||||
type: array
|
||||
oneOf:
|
||||
- contains:
|
||||
const: ALL
|
||||
pattern: '^(?i)ALL$'
|
||||
- allOf:
|
||||
- contains:
|
||||
const: NET_ADMIN
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
- contains:
|
||||
const: CHOWN
|
||||
pattern: '^(?i)CHOWN$'
|
||||
- contains:
|
||||
const: DAC_OVERRIDE
|
||||
pattern: '^(?i)DAC_OVERRIDE$'
|
||||
- contains:
|
||||
const: FSETID
|
||||
pattern: '^(?i)FSETID$'
|
||||
- contains:
|
||||
const: FOWNER
|
||||
pattern: '^(?i)FOWNER$'
|
||||
- contains:
|
||||
const: MKNOD
|
||||
pattern: '^(?i)MKNOD$'
|
||||
- contains:
|
||||
const: NET_RAW
|
||||
pattern: '^(?i)NET_RAW$'
|
||||
- contains:
|
||||
const: SETGID
|
||||
pattern: '^(?i)SETGID$'
|
||||
- contains:
|
||||
const: SETUID
|
||||
pattern: '^(?i)SETUID$'
|
||||
- contains:
|
||||
const: SETFCAP
|
||||
pattern: '^(?i)SETFCAP$'
|
||||
- contains:
|
||||
const: SETPCAP
|
||||
pattern: '^(?i)SETPCAP$'
|
||||
- contains:
|
||||
const: NET_BIND_SERVICE
|
||||
pattern: '^(?i)NET_BIND_SERVICE$'
|
||||
- contains:
|
||||
const: SYS_CHROOT
|
||||
pattern: '^(?i)SYS_CHROOT$'
|
||||
- contains:
|
||||
const: KILL
|
||||
pattern: '^(?i)KILL$'
|
||||
- contains:
|
||||
const: AUDIT_WRITE
|
||||
pattern: '^(?i)AUDIT_WRITE$'
|
||||
mutations:
|
||||
- op: replace
|
||||
path: /securityContext/capabilities
|
||||
value: {"drop": ["ALL"]}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
successMessage: One of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities are used to restrict containers using unwanted privileges
|
||||
FailureMessage: Use one of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities to restrict containers using unwanted privileges
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
podOrContainerSeccompProfile:
|
||||
type: object
|
||||
{{ $podSeccompProfileType := .Polaris.PodSpec.securityContext.seccompProfile.type }}
|
||||
required:
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- securityContext
|
||||
{{ end }}
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
required:
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- seccompProfile
|
||||
{{ end }}
|
||||
properties:
|
||||
seccompProfile:
|
||||
type: object
|
||||
required:
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- type
|
||||
{{ end }}
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
allOf:
|
||||
- not:
|
||||
const: "Unconfined"
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- minLength: 1
|
||||
{{ end }}
|
||||
podOrContainerSELinuxOptions:
|
||||
type: object
|
||||
{{ $podSELinuxOptions := .Polaris.PodSpec.securityContext.seLinuxOptions }}
|
||||
{{ if not $podSELinuxOptions }}
|
||||
required: ["securityContext"]
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
required: ["seLinuxOptions"]
|
||||
properties:
|
||||
seLinuxOptions:
|
||||
type: object
|
||||
minProperties: 1
|
||||
{{ end }}
|
||||
containerDropCapabilities:
|
||||
type: object
|
||||
required: ["securityContext"]
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
required: ["capabilities"]
|
||||
properties:
|
||||
capabilities:
|
||||
type: object
|
||||
required: ["drop"]
|
||||
properties:
|
||||
drop:
|
||||
type: array
|
||||
minItems: 1
|
||||
add:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
not:
|
||||
pattern: '^(?i)ALL$'
|
||||
# End of definitions
|
||||
{{/* Check for AppArmor which uses pod annotations. IF pod fields are missing,
|
||||
require one of the other hardening measures. */}}
|
||||
{{ $annotationName := (print "container.apparmor.security.beta.kubernetes.io/" .Polaris.Container.name) }}
|
||||
{{/* Checking annotations before using index() avoids a nil panic when there are no annotations */}}
|
||||
{{ $annotationExists := false }}
|
||||
{{ if .Polaris.PodTemplate.metadata.annotations }}
|
||||
{{ $annotationExists = index .Polaris "PodTemplate" "metadata" "annotations" $annotationName }}
|
||||
{{ end }}
|
||||
{{ if $annotationExists }}
|
||||
type: object
|
||||
{{ else }}
|
||||
anyOf:
|
||||
- $ref: "#/definitions/podOrContainerSeccompProfile"
|
||||
- $ref: "#/definitions/podOrContainerSELinuxOptions"
|
||||
- $ref: "#/definitions/containerDropCapabilities"
|
||||
{{ end}}
|
||||
@@ -19,3 +19,8 @@ schema:
|
||||
type: object
|
||||
not:
|
||||
const: null
|
||||
mutations:
|
||||
- op: add
|
||||
path: /livenessProbe
|
||||
value: {"exec": { "command": [ "cat", "/tmp/healthy" ] }, "initialDelaySeconds": 5, "periodSeconds": 5 }
|
||||
comment: "TODO: Change the livenessProbe setting to reflect your application's health"
|
||||
|
||||
@@ -25,3 +25,8 @@ schema:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/limits/memory
|
||||
value: "512Mi"
|
||||
comment: "TODO: Set this to the maximum amount of memory you want your workload to use"
|
||||
|
||||
@@ -25,3 +25,8 @@ schema:
|
||||
type: string
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/requests/memory
|
||||
value: "512Mi"
|
||||
comment: "TODO: Set this to the amount of Memory you want to reserve for your workload"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
successMessage: A NetworkPolicy matches pod labels and contains egress and ingress rules
|
||||
failureMessage: A NetworkPolicy should match pod labels and contain applied egress and ingress rules
|
||||
category: Security
|
||||
target: PodTemplate
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
minProperties: 1
|
||||
additionalSchemaStrings:
|
||||
networking.k8s.io/NetworkPolicy: |
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: ["podSelector", "egress", "ingress"]
|
||||
properties:
|
||||
podSelector:
|
||||
type: object
|
||||
required: ["matchLabels"]
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
oneOf:
|
||||
{{ range $key, $value := .Polaris.PodTemplate.metadata.labels }}
|
||||
- properties:
|
||||
"{{ $key }}":
|
||||
type: string
|
||||
const: {{ $value }}
|
||||
required: ["{{ $key }}"]
|
||||
{{ end }}
|
||||
egress:
|
||||
type: array
|
||||
minItems: 1
|
||||
ingress:
|
||||
minItems: 1
|
||||
type: array
|
||||
policyTypes:
|
||||
type: array
|
||||
allOf:
|
||||
- contains:
|
||||
pattern: '^(?i)Egress$'
|
||||
- contains:
|
||||
pattern: '^(?i)Ingress$'
|
||||
@@ -2,7 +2,7 @@ successMessage: Filesystem is read only
|
||||
failureMessage: Filesystem should be read only
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
@@ -40,4 +40,9 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/readOnlyRootFilesystem
|
||||
value: true
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
successMessage: disruptionsAllowed is greater than zero
|
||||
failureMessage: disruptionsAllowed is not greater than zero
|
||||
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:
|
||||
- status
|
||||
- spec
|
||||
properties:
|
||||
status:
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- disruptionsAllowed
|
||||
properties:
|
||||
disruptionsAllowed:
|
||||
type: integer
|
||||
minimum: 1
|
||||
minAvailable:
|
||||
not:
|
||||
const: '100%'
|
||||
maxUnavailable:
|
||||
allOf:
|
||||
- not:
|
||||
const: 0
|
||||
- not:
|
||||
const: '0%'
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
successMessage: Priority class has been set
|
||||
failureMessage: Priority class should be set
|
||||
category: Security
|
||||
target: Pod
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- priorityClassName
|
||||
mutations:
|
||||
- op: add
|
||||
path: /priorityClassName
|
||||
value: high-priority
|
||||
@@ -2,7 +2,7 @@ successMessage: Privilege escalation not allowed
|
||||
failureMessage: Privilege escalation should not be allowed
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
@@ -40,4 +40,9 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/allowPrivilegeEscalation
|
||||
value: false
|
||||
|
||||
@@ -9,3 +9,7 @@ schema:
|
||||
properties:
|
||||
imagePullPolicy:
|
||||
const: Always
|
||||
mutations:
|
||||
- op: add
|
||||
path: /imagePullPolicy
|
||||
value: Always
|
||||
@@ -19,3 +19,8 @@ schema:
|
||||
type: object
|
||||
not:
|
||||
const: null
|
||||
mutations:
|
||||
- op: add
|
||||
path: /readinessProbe
|
||||
value: {"exec": { "command": [ "cat", "/tmp/healthy" ] }, "initialDelaySeconds": 5, "periodSeconds": 5 }
|
||||
comment: "TODO: Change the readinessProbe setting to reflect your application's readiness to serve traffic"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
successMessage: The Role does not allow pods/exec or pods/attach
|
||||
failureMessage: The Role allows Pods/exec or pods/attach
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/Role
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: ""
|
||||
- const: '*'
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
- const: "pods/exec"
|
||||
- const: "pods/attach"
|
||||
verbs:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
# An exec is also possible by `get`ing a web socket.
|
||||
- const: 'get'
|
||||
- const: 'create'
|
||||
@@ -0,0 +1,84 @@
|
||||
successMessage: The RoleBinding does not reference the default cluster-admin ClusterRole or one with wildcard permissions
|
||||
failureMessage: The RoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a Role.
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["kind"]
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
const: "Role"
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["apiGroup", "kind", "name"]
|
||||
properties:
|
||||
apiGroup:
|
||||
type: string
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "ClusterRole"
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
not:
|
||||
const: "cluster-admin"
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
{{ if eq .roleRef.kind "ClusterRole" }}
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .roleRef.name }}"
|
||||
- properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
const: "*"
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
const: '*'
|
||||
verbs:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
oneOf:
|
||||
- contains:
|
||||
type: string
|
||||
const: '*'
|
||||
- minItems: 7
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- "get"
|
||||
- "list"
|
||||
- "watch"
|
||||
- "create"
|
||||
- "update"
|
||||
- "patch"
|
||||
- "delete"
|
||||
{{ end }}
|
||||
@@ -0,0 +1,82 @@
|
||||
successMessage: The RoleBinding does not reference a Role with wildcard permissions
|
||||
failureMessage: The RoleBinding references a Role with wildcard permissions
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a ClusterRole.
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["kind"]
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
const: "ClusterRole"
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["apiGroup", "kind", "name"]
|
||||
properties:
|
||||
apiGroup:
|
||||
type: string
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "Role"
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/Role: |
|
||||
type: object
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
{{ if eq .roleRef.kind "Role" }}
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .roleRef.name }}"
|
||||
- properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
const: "*"
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
const: '*'
|
||||
verbs:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
oneOf:
|
||||
- contains:
|
||||
type: string
|
||||
const: '*'
|
||||
- minItems: 7
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- "get"
|
||||
- "list"
|
||||
- "watch"
|
||||
- "create"
|
||||
- "update"
|
||||
- "patch"
|
||||
- "delete"
|
||||
{{ end }}
|
||||
@@ -0,0 +1,78 @@
|
||||
successMessage: The RoleBinding does not reference a ClusterRole allowing pods/exec or pods/attach
|
||||
failureMessage: The RoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a Role.
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["kind"]
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
const: "Role"
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["apiGroup", "kind", "name"]
|
||||
properties:
|
||||
apiGroup:
|
||||
type: string
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "ClusterRole"
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
{{ if eq .roleRef.kind "ClusterRole" }}
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .roleRef.name }}"
|
||||
- properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: ""
|
||||
- const: '*'
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
- const: "pods/exec"
|
||||
- const: "pods/attach"
|
||||
verbs:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
# An exec is also possible by `get`ing a web socket.
|
||||
- const: 'get'
|
||||
- const: 'create'
|
||||
{{ end }}
|
||||
@@ -0,0 +1,81 @@
|
||||
successMessage: The RoleBinding does not reference a Role allowing Pod exec or attach
|
||||
failureMessage: The RoleBinding references a Role that allows Pods/exec, allows pods/attach, or that does not exist
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a ClusterRole.
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["apiGroup", "kind", "name"]
|
||||
properties:
|
||||
apiGroup:
|
||||
type: string
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "Role"
|
||||
- required: ["roleRef"]
|
||||
properties:
|
||||
roleRef:
|
||||
required: ["apiGroup", "kind", "name"]
|
||||
properties:
|
||||
apiGroup:
|
||||
type: string
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "Role"
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/Role: |
|
||||
type: object
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
{{ if eq .roleRef.kind "Role" }}
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .roleRef.name }}"
|
||||
- properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
not:
|
||||
required: ["apiGroups", "resources", "verbs"]
|
||||
properties:
|
||||
apiGroups:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: ""
|
||||
- const: '*'
|
||||
resources:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
- const: "pods/exec"
|
||||
- const: "pods/attach"
|
||||
verbs:
|
||||
type: array
|
||||
contains:
|
||||
type: string
|
||||
anyOf:
|
||||
- const: '*'
|
||||
# An exec is also possible by `get`ing a web socket.
|
||||
- const: 'get'
|
||||
- const: 'create'
|
||||
{{ end }}
|
||||
@@ -2,7 +2,7 @@ successMessage: Not running as privileged
|
||||
failureMessage: Should not be running as privileged
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
@@ -21,4 +21,8 @@ schema:
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/privileged
|
||||
value: false
|
||||
@@ -2,7 +2,7 @@ successMessage: Is not allowed to run as root
|
||||
failureMessage: Should not be allowed to run as root
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
@@ -50,3 +50,7 @@ schema:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/runAsNonRoot
|
||||
value: true
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
successMessage: The ConfigMap does not contain potentially sensitive content in its keys and values
|
||||
failureMessage: Potentially sensitive content is detected in the ConfigMap keys or values
|
||||
category: Security
|
||||
target: /ConfigMap
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required: ["metadata"]
|
||||
properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
data:
|
||||
type: object
|
||||
propertyNames:
|
||||
'$comment': These ConfigMap keys will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
pattern: '(?i)^AWS_SECRET_ACCESS_KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^GOOGLE_APPLICATION_CREDENTIALS$'
|
||||
- not:
|
||||
pattern: '(?i)^AZURE_.+KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^OCI_CLI_KEY_CONTENT$'
|
||||
- not:
|
||||
pattern: '(?i)password'
|
||||
- not:
|
||||
pattern: '(?i)token'
|
||||
- not:
|
||||
pattern: '(?i)bearer'
|
||||
- not:
|
||||
pattern: '(?i)secret'
|
||||
'$comment': This allows ConfigMap keys not excluded above.
|
||||
- pattern: '(?i).*'
|
||||
additionalProperties:
|
||||
'$comment': These ConfigMap values will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
'$comment': THis matches variations like begin private key, begin rsa private key ...
|
||||
pattern: '(?i)\s*-BEGIN\s+.*PRIVATE KEY-\s*'
|
||||
@@ -0,0 +1,43 @@
|
||||
successMessage: The container does not set potentially sensitive environment variables
|
||||
failureMessage: The container sets potentially sensitive environment variables
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
env:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
'$comment': These environment variable names will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
pattern: '(?i)^AWS_SECRET_ACCESS_KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^GOOGLE_APPLICATION_CREDENTIALS$'
|
||||
- not:
|
||||
pattern: '(?i)^AZURE_.+KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^OCI_CLI_KEY_CONTENT$'
|
||||
- not:
|
||||
pattern: '(?i)password'
|
||||
- not:
|
||||
pattern: '(?i)token'
|
||||
- not:
|
||||
pattern: '(?i)bearer'
|
||||
- not:
|
||||
pattern: '(?i)secret'
|
||||
'$comment': This allows variable names not excluded above.
|
||||
- pattern: '(?i).*'
|
||||
value:
|
||||
type: string
|
||||
'$comment': These environment variable values will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
'$comment': THis matches variations like begin private key, begin rsa private key ...
|
||||
pattern: '(?i)\s*-BEGIN\s+.*PRIVATE KEY-\s*'
|
||||
+98
-24
@@ -22,8 +22,9 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
cfg "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -31,14 +32,20 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
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 (
|
||||
setExitCode bool
|
||||
onlyShowFailedTests bool
|
||||
minScore int
|
||||
auditOutputURL string
|
||||
auditOutputFile string
|
||||
auditOutputFormat string
|
||||
resourceToAudit string
|
||||
useColor bool
|
||||
helmChart string
|
||||
helmValues string
|
||||
checks []string
|
||||
auditNamespace string
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(auditCmd)
|
||||
@@ -52,6 +59,10 @@ func init() {
|
||||
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")
|
||||
auditCmd.PersistentFlags().StringSliceVar(&checks, "checks", []string{}, "Optional flag to specify specific checks to check")
|
||||
auditCmd.PersistentFlags().StringVar(&auditNamespace, "namespace", "", "Namespace to audit. Only applies to in-cluster audits")
|
||||
}
|
||||
|
||||
var auditCmd = &cobra.Command{
|
||||
@@ -62,8 +73,48 @@ var auditCmd = &cobra.Command{
|
||||
if displayName != "" {
|
||||
config.DisplayName = displayName
|
||||
}
|
||||
if len(checks) > 0 {
|
||||
targetChecks := make(map[string]bool)
|
||||
for _, check := range checks {
|
||||
targetChecks[check] = true
|
||||
}
|
||||
for key := range config.Checks {
|
||||
if isTarget := targetChecks[key]; !isTarget {
|
||||
config.Checks[key] = cfg.SeverityIgnore
|
||||
}
|
||||
}
|
||||
}
|
||||
if auditNamespace != "" {
|
||||
if helmChart != "" {
|
||||
logrus.Warn("--namespace and --helm-chart are mutually exclusive. --namespace will be ignored.")
|
||||
}
|
||||
if auditPath != "" {
|
||||
logrus.Warn("--namespace and --audit-path are mutually exclusive. --namespace will be ignored.")
|
||||
}
|
||||
config.Namespace = auditNamespace
|
||||
}
|
||||
if helmChart != "" {
|
||||
var err error
|
||||
auditPath, err = ProcessHelmTemplates(helmChart, helmValues)
|
||||
if err != nil {
|
||||
logrus.Infof("Couldn't process helm chart: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat, useColor)
|
||||
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()
|
||||
@@ -77,26 +128,50 @@ var auditCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string, useColor bool) validator.AuditData {
|
||||
// Create a kubernetes client resource provider
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, workload, c)
|
||||
// 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)
|
||||
}
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(c, k, onlyShowFailedTests)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -155,5 +230,4 @@ func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, wor
|
||||
}
|
||||
}
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright 2020 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/mutation"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const templateLineMarker = "# POLARIS_FIX_TMPL"
|
||||
const templateOpenMarker = "POLARIS_OPEN_TMPL"
|
||||
const templateCloseMarker = "POLARIS_CLOSE_TMPL"
|
||||
|
||||
var (
|
||||
filesPath string
|
||||
checksToFix []string
|
||||
fixAll bool
|
||||
isTemplate bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(fixCommand)
|
||||
fixCommand.PersistentFlags().StringVar(&filesPath, "files-path", "", "mutate and fix one or more YAML files in a specified folder")
|
||||
fixCommand.PersistentFlags().BoolVar(&isTemplate, "template", false, "set to true when modifyng a YAML template, like a Helm chart (experimental)")
|
||||
fixCommand.PersistentFlags().StringSliceVar(&checksToFix, "checks", []string{}, "Optional flag to specify specific checks to fix eg. checks=hostIPCSet,hostPIDSet and checks=all applies fix to all defined checks mutations")
|
||||
}
|
||||
|
||||
var fixCommand = &cobra.Command{
|
||||
Use: "fix",
|
||||
Short: "Fix Infrastructure as code files.",
|
||||
Long: `Fix Infrastructure as code files.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Debug("Setting up controller manager")
|
||||
|
||||
if filesPath == "" {
|
||||
logrus.Error("Please specify a files-path flag")
|
||||
cmd.Help()
|
||||
os.Exit(1)
|
||||
}
|
||||
var yamlFiles []string
|
||||
fileInfo, err := os.Stat(filesPath)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
baseDir := filesPath
|
||||
if !strings.HasSuffix(filesPath, "/") {
|
||||
baseDir = baseDir + "/"
|
||||
}
|
||||
yamlFiles, err = getYamlFiles(baseDir)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
yamlFiles = append(yamlFiles, filesPath)
|
||||
}
|
||||
|
||||
if len(checksToFix) > 0 {
|
||||
if len(checksToFix) == 1 && checksToFix[0] == "all" {
|
||||
allchecks := []string{}
|
||||
for key := range config.Checks {
|
||||
allchecks = append(allchecks, key)
|
||||
}
|
||||
config.Mutations = allchecks
|
||||
} else if len(checksToFix) == 0 && checksToFix[0] == "none" {
|
||||
config.Mutations = nil
|
||||
} else {
|
||||
config.Mutations = checksToFix
|
||||
}
|
||||
}
|
||||
|
||||
for _, fullFilePath := range yamlFiles {
|
||||
yamlContent, err := ioutil.ReadFile(fullFilePath)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error reading file with file path %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error marshalling %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
if isTemplate {
|
||||
yamlContent = []byte(detemplate(string(yamlContent)))
|
||||
}
|
||||
kubeResources := kube.CreateResourceProviderFromYaml(string(yamlContent))
|
||||
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error applying schema check to the resources %s: %v", fullFilePath, err)
|
||||
}
|
||||
allMutations := mutation.GetMutationsFromResults(results)
|
||||
|
||||
updatedYamlContent := ""
|
||||
if len(allMutations) > 0 {
|
||||
for _, resources := range kubeResources.Resources {
|
||||
for _, resource := range resources {
|
||||
key := fmt.Sprintf("%s/%s/%s", resource.Kind, resource.Resource.GetName(), resource.Resource.GetNamespace())
|
||||
mutations := allMutations[key]
|
||||
mutatedYamlContent, err := mutation.ApplyAllMutations(string(resource.OriginalObjectYAML), mutations)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error applying schema mutations to the resource %s: %v", key, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if updatedYamlContent != "" {
|
||||
updatedYamlContent += "\n---\n"
|
||||
}
|
||||
updatedYamlContent += mutatedYamlContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isTemplate {
|
||||
updatedYamlContent = retemplate(updatedYamlContent)
|
||||
}
|
||||
|
||||
if updatedYamlContent != "" {
|
||||
err = ioutil.WriteFile(fullFilePath, []byte(updatedYamlContent), 0644)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error writing output to file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func detemplate(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
for idx, line := range lines {
|
||||
lines[idx] = detemplateLine(line)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func retemplate(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
for idx, line := range lines {
|
||||
lines[idx] = retemplateLine(line)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func detemplateLine(line string) string {
|
||||
if !strings.HasPrefix(strings.TrimSpace(line), "{{") {
|
||||
line = strings.ReplaceAll(line, "{", templateOpenMarker)
|
||||
line = strings.ReplaceAll(line, "}", templateCloseMarker)
|
||||
return line
|
||||
}
|
||||
tmplStart := strings.Index(line, "{{")
|
||||
newLine := line[:tmplStart] + templateLineMarker + line[tmplStart:]
|
||||
return newLine
|
||||
}
|
||||
|
||||
func retemplateLine(line string) string {
|
||||
if !strings.Contains(line, templateLineMarker) {
|
||||
line = strings.ReplaceAll(line, templateOpenMarker, "{")
|
||||
line = strings.ReplaceAll(line, templateCloseMarker, "}")
|
||||
return line
|
||||
}
|
||||
return strings.Replace(line, templateLineMarker, "", 1)
|
||||
}
|
||||
|
||||
func getYamlFiles(rootpath string) ([]string, error) {
|
||||
var list []string
|
||||
err := filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
|
||||
list = append(list, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return list, err
|
||||
}
|
||||
+10
-6
@@ -25,10 +25,11 @@ import (
|
||||
)
|
||||
|
||||
var configPath string
|
||||
var disallowExemptions bool
|
||||
var disallowExemptions, disallowConfigExemptions, disallowAnnotationExemptions, fixChecks bool
|
||||
var logLevel string
|
||||
var auditPath string
|
||||
var displayName string
|
||||
var kubeContext string
|
||||
|
||||
var (
|
||||
version string
|
||||
@@ -37,7 +38,10 @@ 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().StringVarP(&kubeContext, "context", "x", "", "Set the kube context.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any configured exemption.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowConfigExemptions, "disallow-config-exemptions", "", false, "Disallow exemptions set within the configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowAnnotationExemptions, "disallow-annotation-exemptions", "", false, "Disallow any exemption defined as a controller annotation.")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level.")
|
||||
flag.Parse()
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
@@ -63,10 +67,10 @@ var rootCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if disallowExemptions {
|
||||
config.DisallowExemptions = true
|
||||
}
|
||||
|
||||
config.DisallowExemptions = disallowExemptions
|
||||
config.DisallowConfigExemptions = disallowConfigExemptions
|
||||
config.DisallowAnnotationExemptions = disallowAnnotationExemptions
|
||||
config.KubeContext = kubeContext
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Error("You must specify a sub-command.")
|
||||
|
||||
+19
-8
@@ -16,7 +16,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -29,11 +28,17 @@ import (
|
||||
|
||||
var webhookPort int
|
||||
var disableWebhookConfigInstaller bool
|
||||
var enableMutations bool
|
||||
var enableValidations bool
|
||||
var certDir string
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(webhookCmd)
|
||||
webhookCmd.PersistentFlags().IntVarP(&webhookPort, "port", "p", 9876, "Port for the dashboard webserver.")
|
||||
webhookCmd.PersistentFlags().BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false, "disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.")
|
||||
webhookCmd.PersistentFlags().BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false, "Disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.")
|
||||
webhookCmd.PersistentFlags().BoolVar(&enableValidations, "validate", true, "Enable the validating webhook to reject workloads with issues")
|
||||
webhookCmd.PersistentFlags().BoolVar(&enableMutations, "mutate", false, "Enable the mutating webhook to modify workloads with issues")
|
||||
webhookCmd.PersistentFlags().StringVar(&certDir, "cert-dir", "/opt/cert", "Directory in which tls certificate is located")
|
||||
}
|
||||
|
||||
var webhookCmd = &cobra.Command{
|
||||
@@ -44,7 +49,7 @@ var webhookCmd = &cobra.Command{
|
||||
logrus.Debug("Setting up controller manager")
|
||||
|
||||
mgr, err := manager.New(k8sConfig.GetConfigOrDie(), manager.Options{
|
||||
CertDir: "/opt/cert",
|
||||
CertDir: certDir,
|
||||
Port: webhookPort,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -52,19 +57,25 @@ var webhookCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = os.Stat("/opt/cert/tls.crt")
|
||||
_, err = os.Stat(certDir + "/tls.crt")
|
||||
if os.IsNotExist(err) {
|
||||
time.Sleep(time.Second * 10)
|
||||
panic("Cert does not exist")
|
||||
}
|
||||
server := mgr.GetWebhookServer()
|
||||
server.CertName = "tls.crt"
|
||||
server.KeyName = "tls.key"
|
||||
|
||||
// Iterate all the configurations supported controllers to scan and register them for webhooks
|
||||
// Should only register controllers that are configured to be scanned
|
||||
fwebhook.NewWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
|
||||
if !enableMutations && !enableValidations {
|
||||
logrus.Errorf("One of --mutate or --validate must be set to true")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if enableValidations {
|
||||
fwebhook.NewValidateWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
|
||||
}
|
||||
if enableMutations {
|
||||
fwebhook.NewMutateWebhook(mgr, fwebhook.Mutator{Config: config, Client: mgr.GetClient()})
|
||||
}
|
||||
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
|
||||
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
|
||||
logrus.Errorf("Error starting manager: %v", err)
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
---
|
||||
# Source: polaris/templates/0-namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
# required by controller-runtime code doing a cluster wide lookup
|
||||
# when it seems namespace would suffice
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'nodes'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-view
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: view
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/dashboard.service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: polaris-dashboard
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
annotations:
|
||||
spec:
|
||||
ports:
|
||||
- name: http-dashboard
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
type: ClusterIP
|
||||
---
|
||||
# Source: polaris/templates/dashboard.deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: polaris-dashboard
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- polaris
|
||||
- dashboard
|
||||
- --port
|
||||
- "8080"
|
||||
image: 'quay.io/fairwinds/polaris:4.0'
|
||||
imagePullPolicy: 'Always'
|
||||
name: dashboard
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
limits:
|
||||
cpu: 150m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
serviceAccountName: polaris
|
||||
nodeSelector:
|
||||
tolerations:
|
||||
@@ -1,270 +0,0 @@
|
||||
---
|
||||
# Source: polaris/templates/0-namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
# required by controller-runtime code doing a cluster wide lookup
|
||||
# when it seems namespace would suffice
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'nodes'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-view
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: view
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: polaris
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
spec:
|
||||
ports:
|
||||
- name: webhook
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: 9876
|
||||
selector:
|
||||
app: polaris
|
||||
component: webhook
|
||||
type: ClusterIP
|
||||
---
|
||||
# Source: polaris/templates/webhook.deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
component: webhook
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
component: webhook
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
component: webhook
|
||||
spec:
|
||||
containers:
|
||||
- name: webhook
|
||||
command:
|
||||
- polaris
|
||||
- webhook
|
||||
image: 'quay.io/fairwinds/polaris:4.0'
|
||||
imagePullPolicy: 'Always'
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
# These are fairly useless readiness/liveness probes for now
|
||||
# Follow this issue for potential improvements:
|
||||
# https://github.com/kubernetes-sigs/controller-runtime/issues/356
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- ps -ef | grep polaris
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- ps -ef | grep polaris
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: secret
|
||||
mountPath: /opt/cert/
|
||||
readOnly: true
|
||||
- name: cr-logs
|
||||
mountPath: /tmp/
|
||||
readOnly: false
|
||||
serviceAccountName: polaris
|
||||
nodeSelector:
|
||||
tolerations:
|
||||
volumes:
|
||||
- name: secret
|
||||
secret:
|
||||
secretName: polaris
|
||||
- name: cr-logs
|
||||
emptyDir: {}
|
||||
---
|
||||
# Source: polaris/templates/webhook.cert.yaml
|
||||
apiVersion: cert-manager.io/v1alpha2
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: polaris-cert
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
spec:
|
||||
commonName: polaris-webhook.polaris.svc
|
||||
dnsNames:
|
||||
- polaris-webhook.polaris.svc
|
||||
- polaris-webhook.polaris
|
||||
- polaris-webhook
|
||||
- polaris-webhook.polaris.svc.
|
||||
issuerRef:
|
||||
kind: Issuer
|
||||
name: polaris-selfsigned
|
||||
secretName: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.cert.yaml
|
||||
apiVersion: cert-manager.io/v1alpha2
|
||||
kind: Issuer
|
||||
metadata:
|
||||
name: polaris-selfsigned
|
||||
namespace: polaris
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
# Source: polaris/templates/webhook.configuration.yaml
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: ValidatingWebhookConfiguration
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: polaris/polaris-cert
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
service:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
path: /validate
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: polaris.fairwinds.com
|
||||
namespaceSelector:
|
||||
|
||||
matchExpressions:
|
||||
- key: control-plane
|
||||
operator: DoesNotExist
|
||||
objectSelector:
|
||||
|
||||
{}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
apiVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
- v1beta2
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- statefulsets
|
||||
scope: Namespaced
|
||||
- apiGroups:
|
||||
- batch
|
||||
apiVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- jobs
|
||||
- cronjobs
|
||||
scope: Namespaced
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- pods
|
||||
- replicationcontrollers
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: 10
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -27,3 +27,26 @@ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-TM95WXQ');
|
||||
|
||||
!function() {
|
||||
var t = window.driftt = window.drift = window.driftt || [];
|
||||
if (!t.init) {
|
||||
if (t.invoked) return void (window.console && console.error && console.error("Drift snippet included twice."));
|
||||
t.invoked = !0, t.methods = [ "identify", "config", "track", "reset", "debug", "show", "ping", "page", "hide", "off", "on" ],
|
||||
t.factory = function(e) {
|
||||
return function() {
|
||||
var n = Array.prototype.slice.call(arguments);
|
||||
return n.unshift(e), t.push(n), t;
|
||||
};
|
||||
}, t.methods.forEach(function(e) {
|
||||
t[e] = t.factory(e);
|
||||
}), t.load = function(t) {
|
||||
var e = 3e5, n = Math.ceil(new Date() / e) * e, o = document.createElement("script");
|
||||
o.type = "text/javascript", o.async = !0, o.crossorigin = "anonymous", o.src = "https://js.driftt.com/include/" + n + "/" + t + ".js";
|
||||
var i = document.getElementsByTagName("script")[0];
|
||||
i.parentNode.insertBefore(o, i);
|
||||
};
|
||||
}
|
||||
}();
|
||||
drift.SNIPPET_VERSION = '0.3.1';
|
||||
drift.load('dp7v3zbc7xhm');
|
||||
|
||||
@@ -23,6 +23,13 @@
|
||||
color: $successColor;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.2rem solid $warningColor;
|
||||
}
|
||||
blockquote p {
|
||||
color: $warningColor;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom),
|
||||
.page-nav,
|
||||
.page-edit,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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)
|
||||
@@ -20,11 +25,6 @@ If you don't use cert-manager, you'll need to:
|
||||
* Create a TLS secret in your cluster with a valid certificate that uses that CA
|
||||
* Pass the name of that secret with the webhook.secretName parameter.
|
||||
|
||||
### kubectl
|
||||
```bash
|
||||
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/webhook.yaml
|
||||
```
|
||||
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
@@ -45,3 +45,17 @@ output unless we are rejecting a workload altogether.
|
||||
This means that any checks with a severity of `warning` will still pass webhook validation,
|
||||
and the only evidence of that warning will either be in the Polaris dashboard or the
|
||||
Polaris webhook logs. This will change in a future version of Kubernetes.
|
||||
|
||||
## Mutating Webhook
|
||||
By default, the Admission Controller is just pass/fail, but
|
||||
Polaris can also operate as a mutating webhook for many of the issues it checks for.
|
||||
This means Polaris will remediate the issue it finds, rather than rejecting
|
||||
the deployment.
|
||||
|
||||
To enable the mutating webhook, add `--set webhook.mutate=true` to your
|
||||
Helm instlallation command.
|
||||
|
||||
By default, the only mutation enabled is `pullPolicyNotAlways`. If you'd like to
|
||||
enable other mutations, you can set the `webhook.mutations` flag.
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,73 @@
|
||||
---
|
||||
sidebarDepth: 0
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Changelog"
|
||||
|
||||
---
|
||||
## 7.0.2
|
||||
* Fixes for pretty CLI output
|
||||
* Some new checks (disabled by default)
|
||||
* Some additional features in templating engine
|
||||
|
||||
## 7.0.1
|
||||
* Documentation updates
|
||||
|
||||
## 7.0.0
|
||||
* Better support for `polaris fix`
|
||||
* `target: Pod` is now `target: PodSpec` (to differentiate naked Pods from Controllers)
|
||||
|
||||
## 6.0.0
|
||||
* Preliminary support for `polaris fix` command
|
||||
* Changes to how Pod owners are determined
|
||||
* Removed YAML manifests from the `deploy/` directory - Helm is now the default install mechanism
|
||||
|
||||
## 5.1.0
|
||||
* Support `--context` flag for kubecontext
|
||||
* Treat core Kubernetes controllers (`Deployments`, `StatefulSets`, `DaemonSets`, `CronJobs`) as top-level objects, instead of following owner refs
|
||||
|
||||
## 5.0.0
|
||||
* Renamed `multipleReplicasForDeployment` to `deploymentMissingReplicas`
|
||||
* Changed `RunAsRootAllowed` and `hostNetworkSet` default severity to `danger`
|
||||
* Changed `deploymentMissingReplicas` default severity to `warning`
|
||||
|
||||
## 4.2.0
|
||||
* New flags `--disallow-(config|annotation)-exemptions`
|
||||
* Kubernetes dependency updates
|
||||
* Documentation updates
|
||||
## 4.1.0
|
||||
* Handle case-insentitivity in capabilities checks
|
||||
* Change test for PDB disruptions to better handle IaC
|
||||
|
||||
## 4.0.9
|
||||
* Update Alpine base image
|
||||
|
||||
## 4.0.8
|
||||
* Fix support for namespace checks
|
||||
|
||||
## 4.0.7
|
||||
* Fix goreleaser format
|
||||
|
||||
## 4.0.6
|
||||
* Change goreleaser format
|
||||
* Fix `--helm-values` flag
|
||||
|
||||
## 4.0.5
|
||||
* Bugfix for repeated objects on the dashboard
|
||||
|
||||
## 4.0.4
|
||||
* Bugfix for validating webhook and non-pod checks
|
||||
|
||||
## 4.0.3
|
||||
* Fixed bad interaction between `--set-exit-score-below` and `--only-show-failed-tests`
|
||||
* Dependency updates
|
||||
* Support for Helm chart scanning
|
||||
|
||||
## 4.0.2
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.1
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.0
|
||||
* Add support for arbitrary resources, like Ingress or PodDisruptionBudget
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ensure that CPU and memory settings are configured, so that Kubernetes can schedule your workload effectively"
|
||||
---
|
||||
# Efficiency
|
||||
|
||||
These checks ensure that CPU and memory settings are configured, so that
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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,
|
||||
@@ -10,7 +15,7 @@ key | default | description
|
||||
`tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
|
||||
`multipleReplicasForDeployment` | `ignore` | Fails when there is only one replica for a deployment.
|
||||
`deploymentMissingReplicas` | `warning` | Fails when there is only one replica for a deployment.
|
||||
`missingPodDisruptionBudget` | `ignore`
|
||||
|
||||
## Background
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Security Checks Documentation"
|
||||
---
|
||||
# Security
|
||||
|
||||
These checks are related to security concerns. Workloads that fail these
|
||||
|
||||
+16
-5
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Learn your CLI options"
|
||||
---
|
||||
#### CLI Options
|
||||
|
||||
```
|
||||
@@ -14,10 +19,13 @@ webhook
|
||||
Runs the webhook webserver
|
||||
|
||||
# global flags
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
--disallow-exemptions Disallow any exemptions from configuration file.
|
||||
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string Logrus log level. (default "info")
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
-x, --context string Set the kube context.
|
||||
--disallow-exemptions Disallow any exemptions from configuration file.
|
||||
--disallow-config-exemptions Disallow exemptions set within the configuration file.
|
||||
--disallow-annotation-exemptions Disallow any exemption defined as a controller annotation.
|
||||
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string Logrus log level. (default "info")
|
||||
|
||||
# dashboard flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
@@ -30,10 +38,14 @@ webhook
|
||||
|
||||
# audit flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
--checks stringArray Optional flag to specify specific checks to check
|
||||
--color Whether to use color in pretty format. (default true)
|
||||
--display-name string An optional identifier for the audit.
|
||||
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
|
||||
--helm-chart string Will fill out Helm template
|
||||
--helm-values string Optional flag to add helm values
|
||||
-h, --help help for audit
|
||||
--namespace string Namespace to audit. Only applies to in-cluster audits
|
||||
--only-show-failed-tests If specified, audit output will only show failed tests.
|
||||
--output-file string Destination file for audit results.
|
||||
--output-url string Destination URL to send audit results.
|
||||
@@ -41,7 +53,6 @@ webhook
|
||||
--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
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Code of Conduct"
|
||||
---
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
+8
-46
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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.
|
||||
@@ -68,50 +73,7 @@ Each new pull request should:
|
||||
- Be up to date and/or rebased on the master branch
|
||||
|
||||
## Creating a new release
|
||||
To create a new release, simply tag this repo with the new version.
|
||||
|
||||
### Patch releases
|
||||
Patch releases only need to change this repo. The Helm chart and deploy scripts
|
||||
will automatically pull in the latest changes.
|
||||
|
||||
If the release involves changes to anything in the `deploy/` folder (e.g. new RBAC permissions),
|
||||
it needs to be a minor or major release in order to prevent breaking the Helm chart.
|
||||
|
||||
1. Create a PR for this repo
|
||||
1. Bump the version number in:
|
||||
1. main.go
|
||||
2. README.md
|
||||
2. Update CHANGELOG.md
|
||||
3. Merge your PR
|
||||
2. Tag the latest branch for this repo
|
||||
1. Pull the latest commit for the `master` branch (which you just merged in your PR)
|
||||
2. Run `git tag $VERSION && git push --tags`
|
||||
3. Make sure CircleCI runs successfully for the new tag - this will push images to quay.io and create a release in GitHub
|
||||
1. If CircleCI fails, check with Codeowners ASAP
|
||||
|
||||
### Minor/Major releases
|
||||
Minor and major releases need to change both this repository and the
|
||||
[Helm chart repo](https://github.com/FairwindsOps/charts/).
|
||||
|
||||
The steps are:
|
||||
1. Modify the [Helm chart](https://github.com/FairwindsOps/charts/stable/polaris)
|
||||
1. Clone the helm charts repo
|
||||
1. `git clone https://github.com/FairwindsOps/charts`
|
||||
2. `git checkout -b yourname/update-polaris`
|
||||
1. Bump the version number in `stable/polaris/Chart.yaml`
|
||||
2. Make any necessary changes to the chart to support the new version of Polaris (e.g. new RBAC permissions)
|
||||
3. **Don't merge yet!**
|
||||
2. Create a PR for this repo
|
||||
1. Create a new branch named `yourname/update-version`
|
||||
2. Bump the version number in:
|
||||
1. main.go
|
||||
2. README.md
|
||||
3. Regenerate the deployment files. Assuming you've cloned the charts repo to `~/git/charts`:
|
||||
1. `CHARTS_DIR=~/git/charts ./scripts/generate-deployment-files.sh`
|
||||
4. Update CHANGELOG.md
|
||||
5. Merge your PR
|
||||
3. Tag the latest branch for this repo
|
||||
1. Pull the latest for the `master` branch
|
||||
2. Run `git tag $VERSION && git push --tags`
|
||||
3. Make sure CircleCI runs successfully for the new tag - this will push images to quay.io and create a release in GitHub
|
||||
1. If CircleCI fails, check with Codeowners ASAP
|
||||
4. Create and merge a PR for your changes to the Helm chart
|
||||
For major and minor releases, don't forget to update the Helm chart at
|
||||
https://github.com/FairwindsOps/charts
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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).
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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/).
|
||||
@@ -38,10 +43,11 @@ check ID. Note that you'll also have to set its severity in the `checks` section
|
||||
* `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
|
||||
* `Controller`, to check _any_ resource that creates Pods (e.g. Deployments, CronJobs, StatefulSets), as well as naked Pods
|
||||
* `PodTemplate`, same as `Controller`, but the schema applies to the Pod template rather than the top-level controller
|
||||
* `PodSpec`, same as `Controller`, but the schema applies to the Pod spec rather than the top-level controller
|
||||
* `Container` same as `Controller`, but the schema applies to all Container specs rather than the top-level controller
|
||||
* `controllers` - if `target` is `Controller`, `Pod` or `Container`, you can use this to change which types of controllers are checked
|
||||
* `controllers` - if `target` is `Controller`, `PodSpec` or `Container`, you can use this to change which types of controllers are checked
|
||||
* `controllers.include` - _only_ check these controllers
|
||||
* `controllers.exclude` - check all controllers except these
|
||||
* `containers` - if `target` is `Container`, you can use this to decide if `initContainers`, `containers`, or both should be checked
|
||||
@@ -93,6 +99,19 @@ customChecks:
|
||||
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:
|
||||
@@ -116,6 +135,11 @@ schema:
|
||||
const: "{{ .metadata.name }}"
|
||||
```
|
||||
|
||||
* The object available via the go template is the full object, and not limited by `target`.
|
||||
* A check of `target: PodSpec` can directly access the pod specification via the go template variable `.Polaris.PodSpec`.
|
||||
* A check of `target: PodTemplate` can directly access the pod template via the go template variable `.Polaris.PodTemplate`.
|
||||
* A check of `target: Container` can directly access the container being checked via the go template variable `.Polaris.container`. The pod template and pod specification can also be accessed via the respective variables `.Polaris.PodTemplate` and `.Polaris.PodSpec`. Access to pod-level fields allows a container check to consult related fields from the pod, such as `securityContext`.
|
||||
|
||||
You can also use the full [Go template syntax](https://golang.org/pkg/text/template/), though
|
||||
you may need to specify your schema as a string in order to use concepts like `range`. E.g.
|
||||
this check ensures that at least one of the object's labels is present in `matchLabels`:
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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
|
||||
|
||||
+5
-5
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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
|
||||
@@ -11,11 +16,6 @@ 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
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
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)
|
||||
@@ -20,6 +25,32 @@ brew install FairwindsOps/tap/polaris
|
||||
polaris version
|
||||
```
|
||||
|
||||
## Checking Infrastructure as Code files
|
||||
You can audit Kubernetes YAML files by running:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ --format=pretty
|
||||
```
|
||||
This will print out any issues Polaris finds in your manifests.
|
||||
|
||||
Polaris can only check raw YAML manifests. If you'd like to check a Helm template,
|
||||
you can run `helm template` to generate a manifest that Polaris can check.
|
||||
|
||||
## Fixing Issues
|
||||
Polaris can automatically fix many of the issues it finds. For example, you can run
|
||||
```bash
|
||||
polaris fix --files-path ./deploy/ --checks=all
|
||||
```
|
||||
|
||||
to fix any issues inside the `deploy` directory. Polaris may leave
|
||||
comments next to some changes (e.g. liveness and readiness probes) prompting
|
||||
the user to set them to something more appropriate given the context of their
|
||||
application.
|
||||
|
||||
Note that not all issues can be automatically fixed.
|
||||
|
||||
Currently only raw YAML manifests can be mutated. Helm charts etc.
|
||||
still need to be changed manually.
|
||||
|
||||
## Running in a CI pipeline
|
||||
|
||||
### Set minimum score for an exit code
|
||||
@@ -55,6 +86,14 @@ 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
|
||||
|
||||
@@ -75,9 +114,9 @@ The version number of the release tag.
|
||||
##### Example usage
|
||||
|
||||
```yaml
|
||||
uses: fairwindsops/polaris@master
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
with:
|
||||
version: "3.0.3"
|
||||
version: 5.0.0
|
||||
```
|
||||
|
||||
Example inside a job:
|
||||
@@ -86,9 +125,9 @@ Example inside a job:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup polaris
|
||||
uses: fairwindsops/polaris@master
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
with:
|
||||
version: 3.0.3
|
||||
version: 5.0.0
|
||||
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation"
|
||||
---
|
||||
Generated
+18843
-2444
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -8,9 +8,9 @@
|
||||
},
|
||||
"description": "A repository with a Vuepress template for Fairwinds projects",
|
||||
"devDependencies": {
|
||||
"vuepress": "^1.4.0",
|
||||
"vuepress": "^1.9.7",
|
||||
"vuepress-plugin-clean-urls": "^1.1.1",
|
||||
"vuepress-plugin-redirect": "^1.2.3"
|
||||
"vuepress-plugin-redirect": "^1.2.5"
|
||||
},
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
@@ -26,7 +26,8 @@
|
||||
"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",
|
||||
"build:metadata": "cat main-metadata.md > README.md || true",
|
||||
"build:readme": "npm run build:metadata && cat ../README.md | grep -v 'ocumentation' | sed \"s/https:\\/\\/\\w\\+.docs.fairwinds.com//g\" >> README.md",
|
||||
"check-links": "vuepress check-md",
|
||||
"serve": "npm run build:readme && vuepress dev --port 3003",
|
||||
"vuepress": "vuepress"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
checks:
|
||||
# reliability
|
||||
multipleReplicasForDeployment: warning
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: warning
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
@@ -12,20 +12,31 @@ checks:
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
# security
|
||||
automountServiceAccountToken: warning
|
||||
hostIPCSet: danger
|
||||
hostPIDSet: danger
|
||||
linuxHardening: danger
|
||||
missingNetworkPolicy: warning
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
runAsRootAllowed: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: warning
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
sensitiveContainerEnvVar: danger
|
||||
sensitiveConfigmapContent: danger
|
||||
clusterrolePodExecAttach: danger
|
||||
rolePodExecAttach: danger
|
||||
clusterrolebindingPodExecAttach: danger
|
||||
rolebindingClusterRolePodExecAttach: danger
|
||||
rolebindingRolePodExecAttach: danger
|
||||
# custom
|
||||
resourceLimits: warning
|
||||
imageRegistry: danger
|
||||
|
||||
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- my-network-controller
|
||||
@@ -86,4 +97,3 @@ customChecks:
|
||||
type: string
|
||||
not:
|
||||
pattern: ^quay.io
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
checks:
|
||||
# reliability
|
||||
multipleReplicasForDeployment: ignore
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: ignore
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
@@ -20,14 +20,17 @@ checks:
|
||||
hostPIDSet: danger
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
runAsRootAllowed: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: warning
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
tlsSettingsMissing: warning
|
||||
|
||||
mutations:
|
||||
- pullPolicyNotAlways
|
||||
|
||||
exemptions:
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
options:
|
||||
organization: fairwinds-opensource
|
||||
organization: fairwinds-production
|
||||
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,28 +1,95 @@
|
||||
module github.com/fairwindsops/polaris
|
||||
|
||||
go 1.15
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.74.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.15 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.10 // indirect
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/gobuffalo/packr/v2 v2.8.1
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.1.3 // indirect
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/gobuffalo/packr/v2 v2.8.3
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/karrick/godirwalk v1.16.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/thoas/go-funk v0.8.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
k8s.io/api v0.21.1
|
||||
k8s.io/apimachinery v0.21.1
|
||||
k8s.io/client-go v0.21.1
|
||||
sigs.k8s.io/controller-runtime v0.9.0-alpha.1
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/thoas/go-funk v0.9.2
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.25.0
|
||||
k8s.io/apimachinery v0.25.0
|
||||
k8s.io/client-go v0.25.0
|
||||
sigs.k8s.io/controller-runtime v0.13.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.9.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gobuffalo/logger v1.0.7 // indirect
|
||||
github.com/gobuffalo/packd v1.0.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/karrick/godirwalk v1.17.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/markbates/errx v1.1.0 // indirect
|
||||
github.com/markbates/oncer v1.0.0 // indirect
|
||||
github.com/markbates/safe v1.0.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
|
||||
golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/component-base v0.25.0 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect
|
||||
k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
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 = "4.0.2"
|
||||
var (
|
||||
// Version is set during build
|
||||
Version = "development"
|
||||
// Commit is set during build
|
||||
Commit = ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
+28
-1
@@ -1,3 +1,17 @@
|
||||
// Copyright 2022 FairwindsOps, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -13,11 +27,12 @@ var (
|
||||
// tests as we migrate toward JSON schema
|
||||
checkOrder = []string{
|
||||
// Controller Checks
|
||||
"multipleReplicasForDeployment",
|
||||
"deploymentMissingReplicas",
|
||||
// Pod checks
|
||||
"hostIPCSet",
|
||||
"hostPIDSet",
|
||||
"hostNetworkSet",
|
||||
"automountServiceAccountToken",
|
||||
// Container checks
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
@@ -35,11 +50,23 @@ var (
|
||||
"dangerousCapabilities",
|
||||
"insecureCapabilities",
|
||||
"priorityClassNotSet",
|
||||
"linuxHardening",
|
||||
"sensitiveContainerEnvVar",
|
||||
// Other checks
|
||||
"tlsSettingsMissing",
|
||||
"pdbDisruptionsIsZero",
|
||||
"metadataAndNameMismatched",
|
||||
"missingPodDisruptionBudget",
|
||||
"missingNetworkPolicy",
|
||||
"sensitiveConfigmapContent",
|
||||
"clusterrolePodExecAttach",
|
||||
"rolePodExecAttach",
|
||||
"clusterrolebindingPodExecAttach",
|
||||
"rolebindingClusterRolePodExecAttach",
|
||||
"rolebindingRolePodExecAttach",
|
||||
"clusterrolebindingClusterAdmin",
|
||||
"rolebindingClusterAdminClusterRole",
|
||||
"rolebindingClusterAdminRole",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
+10
-5
@@ -29,11 +29,16 @@ import (
|
||||
|
||||
// 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"`
|
||||
Mutations []string `json:"mutations"`
|
||||
KubeContext string `json:"kubeContext"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// Exemption represents an exemption to normal rules
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2022 FairwindsOps, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -11,7 +25,7 @@ func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, con
|
||||
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 {
|
||||
|
||||
@@ -25,40 +25,40 @@ import (
|
||||
|
||||
var confContainerTest = `
|
||||
checks:
|
||||
multipleReplicasForDeployment: warning
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: warning
|
||||
pullPolicyNotAlways: warning
|
||||
exemptions:
|
||||
- namespace: prometheus
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- deploymentMissingReplicas
|
||||
- controllerNames:
|
||||
- controller2
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- deploymentMissingReplicas
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- controller3
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- deploymentMissingReplicas
|
||||
- containerNames:
|
||||
- container41
|
||||
- container42
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- deploymentMissingReplicas
|
||||
- namespace: kube-system
|
||||
containerNames:
|
||||
- container51
|
||||
- container52
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- deploymentMissingReplicas
|
||||
- controllerNames:
|
||||
- controller6
|
||||
containerNames:
|
||||
- container61
|
||||
- container62
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- deploymentMissingReplicas
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- controller7
|
||||
@@ -66,7 +66,7 @@ exemptions:
|
||||
- container71
|
||||
- container72
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- deploymentMissingReplicas
|
||||
- priorityClassNotSet
|
||||
- namespace: polaris
|
||||
`
|
||||
@@ -86,22 +86,22 @@ func TestNamespaceExemptionForSpecifiedRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "")
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", ""), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "container11")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller1"), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "container11")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", ""), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "")
|
||||
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("multipleReplicasForDeployment", createMeta("kube-system", ""), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -109,16 +109,16 @@ func TestNamespaceExemptionForAllRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "")
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", ""), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "container11")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", "controller1"), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "container11")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", ""), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", "controller1"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("polaris", "controller1"), "")
|
||||
@@ -129,28 +129,28 @@ func TestControllerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "")
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller2"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "container21")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller2"), "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "container21")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller2"), "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller2"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller3"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller3"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller3"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "container31")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller3"), "container31")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller4"), "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -158,22 +158,22 @@ func TestOnlyContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container41")
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container42")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container42")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller4"), "container41")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller4"), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container41")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "container41")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller4"), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -181,25 +181,25 @@ func TestNamespaceAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container51")
|
||||
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("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "container51")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller5"), "container51")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller5"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", ""), "container51")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("insights-agent", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller5"), "container51")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller5"), "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -207,25 +207,25 @@ func TestControllerAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller6"), "container61")
|
||||
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("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container61")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "container61")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container61")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller7"), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container61")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container61")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container61")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -233,28 +233,28 @@ func TestContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container71")
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container71")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container71")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller7"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container71")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "container71")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", "controller7"), "container71")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("insights-agent", "controller7"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container71")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container61")
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", "controller7"), "container71")
|
||||
|
||||
+49
-9
@@ -1,3 +1,17 @@
|
||||
// Copyright 2022 FairwindsOps, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -24,15 +38,26 @@ const (
|
||||
TargetController TargetKind = "Controller"
|
||||
// TargetContainer points to the container spec
|
||||
TargetContainer TargetKind = "Container"
|
||||
// TargetPod points to the pod spec
|
||||
TargetPod TargetKind = "Pod"
|
||||
// TargetPodSpec points to the pod spec
|
||||
TargetPodSpec TargetKind = "PodSpec"
|
||||
// TargetPodTemplate points to the pod template
|
||||
TargetPodTemplate TargetKind = "PodTemplate"
|
||||
)
|
||||
|
||||
// HandledTargets is a list of target names that are explicitly handled
|
||||
var HandledTargets = []TargetKind{
|
||||
TargetController,
|
||||
TargetContainer,
|
||||
TargetPod,
|
||||
TargetPodSpec,
|
||||
TargetPodTemplate,
|
||||
}
|
||||
|
||||
// Mutation defines how to change a YAML file, in the style of JSON Patch
|
||||
type Mutation struct {
|
||||
Path string
|
||||
Op string
|
||||
Value interface{}
|
||||
Comment string
|
||||
}
|
||||
|
||||
// SchemaCheck is a Polaris check that runs using JSON Schema
|
||||
@@ -51,12 +76,14 @@ type SchemaCheck struct {
|
||||
AdditionalSchemas map[string]map[string]interface{} `yaml:"additionalSchemas" json:"additionalSchemas"`
|
||||
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
|
||||
AdditionalValidators map[string]jsonschema.RootSchema `yaml:"-" json:"-"`
|
||||
Mutations []Mutation `yaml:"mutations" json:"mutations"`
|
||||
}
|
||||
|
||||
type resourceMinimum string
|
||||
type resourceMaximum string
|
||||
|
||||
func unmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
|
||||
// UnmarshalYAMLOrJSON is a helper function to unmarshal data in an arbitrary format
|
||||
func UnmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
|
||||
reader := bytes.NewReader(raw)
|
||||
d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
|
||||
for {
|
||||
@@ -73,7 +100,7 @@ func unmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
|
||||
// ParseCheck parses a check from a byte array
|
||||
func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
|
||||
check := SchemaCheck{}
|
||||
err := unmarshalYAMLOrJSON(rawBytes, &check)
|
||||
err := UnmarshalYAMLOrJSON(rawBytes, &check)
|
||||
if err != nil {
|
||||
return check, err
|
||||
}
|
||||
@@ -167,6 +194,9 @@ func (check *SchemaCheck) Initialize(id string) error {
|
||||
}
|
||||
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 {
|
||||
@@ -214,24 +244,29 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
|
||||
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
|
||||
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
|
||||
val := jsonschema.RootSchema{}
|
||||
err := unmarshalYAMLOrJSON([]byte(schemaStr), &val)
|
||||
err := UnmarshalYAMLOrJSON([]byte(schemaStr), &val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newCheck.AdditionalValidators[kind] = val
|
||||
}
|
||||
err := unmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
|
||||
err := UnmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newCheck, err
|
||||
}
|
||||
|
||||
// CheckPod checks a pod spec against the schema
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
|
||||
// CheckPodSpec checks a pod spec against the schema
|
||||
func (check SchemaCheck) CheckPodSpec(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(pod)
|
||||
}
|
||||
|
||||
// CheckPodTemplate checks a pod template against the schema
|
||||
func (check SchemaCheck) CheckPodTemplate(podTemplate interface{}) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(podTemplate)
|
||||
}
|
||||
|
||||
// CheckController checks a controler's spec against the schema
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
@@ -278,6 +313,11 @@ func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []inte
|
||||
// IsActionable decides if this check applies to a particular target
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
|
||||
if funk.Contains(HandledTargets, target) {
|
||||
if check.Target == TargetPodTemplate && target == TargetPodSpec {
|
||||
// A target=PodSpec and check.Target=PodTemplate is expected
|
||||
// because applyPodSchemaChecks() explicitly sets check.Target
|
||||
return true
|
||||
}
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
padding-right: 40px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights img {
|
||||
max-width: 400px;
|
||||
.card.transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights a {
|
||||
background-color: #20162D;
|
||||
|
||||
@@ -1,421 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -130,13 +130,17 @@ func writeTemplate(tmpl *template.Template, data *templateData, w http.ResponseW
|
||||
|
||||
func getConfigForQuery(base config.Configuration, query url.Values) config.Configuration {
|
||||
c := base
|
||||
exemptions := query.Get("disallowExemptions")
|
||||
if exemptions == "false" {
|
||||
c.DisallowExemptions = false
|
||||
}
|
||||
if exemptions == "true" {
|
||||
switch query.Get("disallowExemptions") {
|
||||
case "true":
|
||||
c.DisallowExemptions = true
|
||||
c.DisallowConfigExemptions = true
|
||||
c.DisallowAnnotationExemptions = true
|
||||
default:
|
||||
c.DisallowExemptions = false
|
||||
c.DisallowConfigExemptions = false
|
||||
c.DisallowAnnotationExemptions = false
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -181,7 +185,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
}
|
||||
|
||||
var auditDataObj validator.AuditData
|
||||
auditDataObj, err = validator.RunAudit(adjustedConf, k, false)
|
||||
auditDataObj, err = validator.RunAudit(adjustedConf, k)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -200,12 +204,14 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" && r.URL.Path != basePath {
|
||||
logrus.Warningf("Path not found: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
adjustedConf := getConfigForQuery(c, r.URL.Query())
|
||||
|
||||
if auditData == nil {
|
||||
logrus.Infof("Creating resource provider")
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
@@ -213,15 +219,18 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
return
|
||||
}
|
||||
|
||||
logrus.Infof("Running audit")
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(adjustedConf, k, false)
|
||||
auditData, err = validator.RunAudit(adjustedConf, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting audit data: %v", err)
|
||||
http.Error(w, "Error running audit", 500)
|
||||
return
|
||||
}
|
||||
logrus.Infof("Sending results")
|
||||
MainHandler(w, r, adjustedConf, auditData, basePath)
|
||||
} else {
|
||||
logrus.Infof("Sending results")
|
||||
MainHandler(w, r, adjustedConf, *auditData, basePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2022 FairwindsOps, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
@@ -238,4 +252,3 @@ func TestStringInSlice(t *testing.T) {
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, true, actual)
|
||||
}
|
||||
|
||||
|
||||
@@ -83,10 +83,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="insights" class="card insights py-2">
|
||||
<img class="align-self-center" src="static/images/FW_Insights_Polaris.svg" />
|
||||
<span>Fairwinds, the company behind Polaris, now offers Fairwinds Insights, a multi-cluster, multi-user Kubernetes configuration validation and policy enforcement platform. Fairwinds Insights can help you avoid errors that lead to wasted time, compute costs, and increased risk.</span>
|
||||
<a class="col-auto py-1 px-3 mt-2 align-self-center" href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">Try Insights</a>
|
||||
<div class="card transparent">
|
||||
<a class="align-self-center"
|
||||
target="_blank"
|
||||
href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="static/images/benchmark.png" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="categories" class="card category">
|
||||
@@ -134,7 +136,11 @@
|
||||
|
||||
{{ range $namespace, $results := .FilteredAuditData.GetResultsByNamespace }}
|
||||
<div id="{{ $namespace }}" class="card namespace">
|
||||
{{ if eq $namespace "" }}
|
||||
<h3>Cluster Resources</h3>
|
||||
{{ else }}
|
||||
<h3>Namespace: <strong>{{ $namespace }}</strong></h3>
|
||||
{{ end }}
|
||||
<div class="expandable-table">
|
||||
{{ range $index, $result := $results }}
|
||||
<div class="resource-info">
|
||||
|
||||
+107
-16
@@ -1,3 +1,17 @@
|
||||
// Copyright 2022 FairwindsOps, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package kube
|
||||
|
||||
import (
|
||||
@@ -6,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/thoas/go-funk"
|
||||
"gopkg.in/yaml.v3"
|
||||
kubeAPICoreV1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
@@ -15,40 +30,53 @@ import (
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
// There are often many ReplicaSets that are orphaned (default 10 per deployment) so we don't cache all of them
|
||||
var skipListCacheKinds = []string{"ReplicaSet", "Job"}
|
||||
|
||||
// GenericResource is a base implementation with some free methods for inherited structs
|
||||
type GenericResource struct {
|
||||
Kind string
|
||||
ObjectMeta kubeAPIMetaV1.Object
|
||||
Resource unstructured.Unstructured
|
||||
PodSpec *kubeAPICoreV1.PodSpec
|
||||
PodTemplate interface{}
|
||||
OriginalObjectJSON []byte
|
||||
OriginalObjectYAML []byte
|
||||
}
|
||||
|
||||
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
|
||||
func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (GenericResource, error) {
|
||||
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecMap interface{}) (GenericResource, error) {
|
||||
if unst.GetCreationTimestamp().Time.IsZero() {
|
||||
unstructured.RemoveNestedField(unst.Object, "metadata", "creationTimestamp")
|
||||
unstructured.RemoveNestedField(unst.Object, "status")
|
||||
}
|
||||
workload := GenericResource{
|
||||
Kind: unst.GetKind(),
|
||||
Resource: *unst,
|
||||
Resource: unst,
|
||||
}
|
||||
|
||||
objMeta, err := meta.Accessor(unst)
|
||||
objMeta, err := meta.Accessor(&unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.ObjectMeta = objMeta
|
||||
workload.PodTemplate, err = GetPodTemplate(unst.UnstructuredContent())
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(unst)
|
||||
b, err := json.Marshal(&unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = b
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
podSpecMap := GetPodSpec(m)
|
||||
if podSpecMap == nil {
|
||||
podSpecMap = GetPodSpec(m)
|
||||
}
|
||||
if podSpecMap != nil {
|
||||
b, err = json.Marshal(podSpecMap)
|
||||
if err != nil {
|
||||
@@ -61,16 +89,20 @@ func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (Generi
|
||||
}
|
||||
workload.PodSpec = &podSpec
|
||||
}
|
||||
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents
|
||||
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) {
|
||||
podMap, err := SerializePod(&podResource)
|
||||
if err != nil {
|
||||
return GenericResource{}, err
|
||||
}
|
||||
workload := GenericResource{
|
||||
Kind: "Pod",
|
||||
PodSpec: &podResource.Spec,
|
||||
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
|
||||
Kind: "Pod",
|
||||
PodSpec: &podResource.Spec,
|
||||
PodTemplate: podMap,
|
||||
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
|
||||
}
|
||||
if originalObject != nil {
|
||||
bytes, err := json.Marshal(originalObject)
|
||||
@@ -101,7 +133,9 @@ func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
|
||||
if err != nil {
|
||||
return GenericResource{}, err
|
||||
}
|
||||
return NewGenericResourceFromUnstructured(&unst)
|
||||
res, err := NewGenericResourceFromUnstructured(unst, nil)
|
||||
res.OriginalObjectYAML = contentBytes
|
||||
return res, err
|
||||
}
|
||||
|
||||
// ResolveControllerFromPod builds a new workload for a given Pod
|
||||
@@ -123,6 +157,8 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
}
|
||||
topKind := "Pod"
|
||||
topMeta := podWorkload.ObjectMeta
|
||||
var topPodSpec interface{}
|
||||
topPodSpec = podWorkload.Resource.Object
|
||||
owners := podResource.ObjectMeta.GetOwnerReferences()
|
||||
lastKey := ""
|
||||
for len(owners) > 0 {
|
||||
@@ -138,7 +174,12 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
lastKey = key
|
||||
abstractObject, ok := objectCache[key]
|
||||
if !ok {
|
||||
err := cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
|
||||
var err error
|
||||
if funk.Contains(skipListCacheKinds, firstOwner.Kind) {
|
||||
err = cacheSingleObject(ctx, firstOwner.APIVersion, firstOwner.Kind, topMeta.GetNamespace(), firstOwner.Name, dynamicClient, restMapper, objectCache)
|
||||
} else {
|
||||
err = cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Warnf("Error caching objects of Kind %s %v", firstOwner.Kind, err)
|
||||
break
|
||||
@@ -155,13 +196,17 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
logrus.Warnf("Error retrieving parent metadata %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err)
|
||||
return GenericResource{}, err
|
||||
}
|
||||
podSpec := GetPodSpec(abstractObject.Object)
|
||||
if podSpec != nil {
|
||||
topPodSpec = podSpec
|
||||
}
|
||||
topMeta = objMeta
|
||||
owners = abstractObject.GetOwnerReferences()
|
||||
}
|
||||
|
||||
if lastKey != "" {
|
||||
unst := objectCache[lastKey]
|
||||
return NewGenericResourceFromUnstructured(&unst)
|
||||
return NewGenericResourceFromUnstructured(unst, topPodSpec)
|
||||
}
|
||||
workload, err := NewGenericResourceFromPod(podResource, podResource)
|
||||
if err != nil {
|
||||
@@ -172,7 +217,21 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
func cacheSingleObject(ctx context.Context, apiVersion, kind, namespace, name string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
logrus.Debugf("Caching a single %s", kind)
|
||||
object, err := getObject(ctx, namespace, kind, apiVersion, name, dynamicClient, restMapper)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving object %s/%s/%s/%s because of error: %v", kind, apiVersion, namespace, name, err)
|
||||
return err
|
||||
}
|
||||
key := fmt.Sprintf("%s/%s/%s", object.GetKind(), object.GetNamespace(), object.GetName())
|
||||
logrus.Debugf("Caching key %s", key)
|
||||
objectCache[key] = *object
|
||||
return nil
|
||||
}
|
||||
|
||||
func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
logrus.Debugf("Caching all %s", kind)
|
||||
fqKind := schema.FromAPIVersionAndKind(apiVersion, kind)
|
||||
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
|
||||
if err != nil {
|
||||
@@ -187,14 +246,15 @@ func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamic
|
||||
}
|
||||
for idx, object := range objects.Items {
|
||||
key := fmt.Sprintf("%s/%s/%s", object.GetKind(), object.GetNamespace(), object.GetName())
|
||||
logrus.Debugf(" caching key %s", key)
|
||||
objectCache[key] = objects.Items[idx]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getObject(ctx context.Context, namespace, kind, version, name string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper) (*unstructured.Unstructured, error) {
|
||||
fqKind := schema.ParseGroupKind(kind)
|
||||
mapping, err := (*restMapper).RESTMapping(fqKind, version)
|
||||
fqKind := schema.FromAPIVersionAndKind(version, kind)
|
||||
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -214,3 +274,34 @@ func GetPodSpec(yaml map[string]interface{}) interface{} {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodTemplate looks inside arbitrary YAML for a Pod template, containing
|
||||
// fields `spec.containers`.
|
||||
// For example, it returns the `spec.template` level of a Kubernetes Deployment yaml.
|
||||
func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err error) {
|
||||
if yamlSpec, ok := yaml["spec"]; ok {
|
||||
if yamlSpecMap, ok := yamlSpec.(map[string]interface{}); ok {
|
||||
if _, ok := yamlSpecMap["containers"]; ok {
|
||||
// This is a hack around unstructured.SetNestedField using DeepCopy which does
|
||||
// not support the type int, and panics.
|
||||
// Related: https://github.com/kubernetes/kubernetes/issues/62769
|
||||
podTemplateJSON, err := json.Marshal(yaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podTemplateMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(podTemplateJSON, &podTemplateMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return podTemplateMap, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, podSpecField := range podSpecFields {
|
||||
if childYaml, ok := yaml[podSpecField]; ok {
|
||||
return GetPodTemplate(childYaml.(map[string]interface{}))
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
+132
-25
@@ -1,8 +1,23 @@
|
||||
// Copyright 2022 FairwindsOps, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -44,7 +59,12 @@ type resourceKindMap map[string][]GenericResource
|
||||
|
||||
func (rkm resourceKindMap) addResource(r GenericResource) {
|
||||
gvk := r.Resource.GroupVersionKind()
|
||||
key := gvk.Group + "/" + gvk.Kind
|
||||
var key string
|
||||
if gvk.Group != "" {
|
||||
key = gvk.Group + "/" + gvk.Kind
|
||||
} else {
|
||||
key = gvk.Kind
|
||||
}
|
||||
rkm[key] = append(rkm[key], r)
|
||||
}
|
||||
|
||||
@@ -168,7 +188,7 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
|
||||
logrus.Errorf("Could not find workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(obj)
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(*obj, nil)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not parse workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
@@ -201,7 +221,11 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
logrus.Errorf("Error reading file: %v", path)
|
||||
return err
|
||||
}
|
||||
return resources.addResourcesFromYaml(string(contents))
|
||||
err = resources.addResourcesFromYaml(string(contents))
|
||||
if err != nil {
|
||||
logrus.Warnf("Skipping %s: cannot add resource from YAML: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(directory, visitFile)
|
||||
@@ -211,9 +235,16 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
return &resources, nil
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromYaml returns a new ResourceProvider using the yaml
|
||||
func CreateResourceProviderFromYaml(yamlContent string) *ResourceProvider {
|
||||
resources := newResourceProvider("unknown", "Content", "unknown")
|
||||
resources.addResourcesFromYaml(string(yamlContent))
|
||||
return &resources
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromCluster creates a new ResourceProvider using live data from a cluster
|
||||
func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
kubeConf, configError := config.GetConfigWithContext(c.KubeContext)
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
return nil, configError
|
||||
@@ -239,24 +270,47 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
logrus.Errorf("Error fetching Cluster API version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
provider := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Cluster", clusterName)
|
||||
|
||||
sourceType := "Cluster"
|
||||
if c.Namespace != "" {
|
||||
logrus.Debug("namespace is specififed in config, setting source type to ClusterNamespace")
|
||||
sourceType = "ClusterNamespace"
|
||||
}
|
||||
provider := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, sourceType, clusterName)
|
||||
|
||||
logrus.Info("Loading nodes")
|
||||
nodes, err := kube.CoreV1().Nodes().List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Nodes: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
namespaces, err := kube.CoreV1().Namespaces().List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Namespaces: %v", err)
|
||||
return nil, err
|
||||
|
||||
logrus.Info("Loading namespaces")
|
||||
var namespaces *corev1.NamespaceList
|
||||
if c.Namespace != "" {
|
||||
ns, err := kube.CoreV1().Namespaces().Get(ctx, c.Namespace, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespaces = &corev1.NamespaceList{
|
||||
Items: []corev1.Namespace{*ns},
|
||||
}
|
||||
} else {
|
||||
nsList, err := kube.CoreV1().Namespaces().List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Namespaces: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
namespaces = nsList
|
||||
}
|
||||
pods, err := kube.CoreV1().Pods("").List(ctx, listOpts)
|
||||
logrus.Info("Loading pods")
|
||||
pods, err := kube.CoreV1().Pods(c.Namespace).List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Info("Setting up restmapper")
|
||||
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting API Group resources: %v", err)
|
||||
@@ -277,13 +331,17 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
for key := range check.AdditionalSchemas {
|
||||
neededKinds = append(neededKinds, conf.TargetKind(key))
|
||||
}
|
||||
for key := range check.AdditionalSchemaStrings {
|
||||
neededKinds = append(neededKinds, conf.TargetKind(key))
|
||||
}
|
||||
for _, kind := range neededKinds {
|
||||
if !funk.Contains(conf.HandledTargets, kind) {
|
||||
if !funk.Contains(conf.HandledTargets, kind) && !funk.Contains(additionalKinds, kind) {
|
||||
additionalKinds = append(additionalKinds, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var kubernetesResources []GenericResource
|
||||
for _, kind := range additionalKinds {
|
||||
groupKind := parseGroupKind(maybeTransformKindIntoGroupKind(string(kind)))
|
||||
mapping, err := (restMapper).RESTMapping(groupKind)
|
||||
@@ -292,60 +350,67 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects, err := (*dynamic).Resource(mapping.Resource).Namespace("").List(ctx, metav1.ListOptions{})
|
||||
logrus.Info("Loading " + kind)
|
||||
objects, err := (*dynamic).Resource(mapping.Resource).Namespace(c.Namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range objects.Items {
|
||||
res, err := NewGenericResourceFromUnstructured(&obj)
|
||||
res, err := NewGenericResourceFromUnstructured(obj, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider.Resources.addResource(res)
|
||||
kubernetesResources = append(kubernetesResources, res)
|
||||
}
|
||||
}
|
||||
|
||||
objectCache := map[string]unstructured.Unstructured{}
|
||||
|
||||
logrus.Info("Loading controllers")
|
||||
controllers, err := LoadControllers(ctx, pods.Items, dynamic, &restMapper, objectCache)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading controllers from pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
// resources loaded from custom checks can also contain controllers and thus would be added twice to the provider
|
||||
kubernetesResources = deduplicateControllers(append(kubernetesResources, controllers...))
|
||||
|
||||
provider.Nodes = nodes.Items
|
||||
provider.Namespaces = namespaces.Items
|
||||
provider.Resources.addResources(controllers)
|
||||
provider.Resources.addResources(kubernetesResources)
|
||||
logrus.Info("Done loading Kubernetes resources")
|
||||
return &provider, nil
|
||||
}
|
||||
|
||||
// LoadControllers loads a list of controllers from the kubeResources Pods
|
||||
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
|
||||
interfaces := []GenericResource{}
|
||||
deduped := map[string]corev1.Pod{}
|
||||
for _, pod := range pods {
|
||||
deduped := map[string]*corev1.Pod{}
|
||||
for idx, pod := range pods {
|
||||
owners := pod.ObjectMeta.OwnerReferences
|
||||
if len(owners) == 0 {
|
||||
deduped[pod.ObjectMeta.Namespace+"/Pod/"+pod.ObjectMeta.Name] = pod
|
||||
deduped[pod.ObjectMeta.Namespace+"/Pod/"+pod.ObjectMeta.Name] = &pods[idx]
|
||||
continue
|
||||
}
|
||||
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod
|
||||
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = &pods[idx]
|
||||
}
|
||||
for _, pod := range deduped {
|
||||
workload, err := ResolveControllerFromPod(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
for key, pod := range deduped {
|
||||
logrus.Debugf("Resolving controller from pod %s", key)
|
||||
workload, err := ResolveControllerFromPod(ctx, *pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
interfaces = append(interfaces, workload)
|
||||
}
|
||||
return deduplicateControllers(interfaces), nil
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates.
|
||||
// In cases like CronJobs older children can hang around, so this takes the most recent.
|
||||
func deduplicateControllers(inputControllers []GenericResource) []GenericResource {
|
||||
func deduplicateControllers(inputResources []GenericResource) []GenericResource {
|
||||
controllerMap := make(map[string]GenericResource)
|
||||
for _, controller := range inputControllers {
|
||||
for _, controller := range inputResources {
|
||||
key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName()
|
||||
oldController, ok := controllerMap[key]
|
||||
if !ok || controller.ObjectMeta.GetCreationTimestamp().Time.After(oldController.ObjectMeta.GetCreationTimestamp().Time) {
|
||||
@@ -396,7 +461,6 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
|
||||
decoder = k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Invalid YAML: %s", string(contents))
|
||||
return err
|
||||
}
|
||||
if resource.Kind == "Namespace" {
|
||||
@@ -415,6 +479,7 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workload.OriginalObjectYAML = contentBytes
|
||||
resources.Resources.addResource(workload)
|
||||
} else {
|
||||
newResource, err := NewGenericResourceFromBytes(contentBytes)
|
||||
@@ -425,3 +490,45 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SerializePodSpec converts a typed PodSpec into a map[string]interface{}
|
||||
func SerializePodSpec(pod *corev1.PodSpec) (map[string]interface{}, error) {
|
||||
podJSON, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(podJSON, &podMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return podMap, nil
|
||||
}
|
||||
|
||||
// SerializePod converts a typed Pod into a map[string]interface{}
|
||||
func SerializePod(pod *corev1.Pod) (map[string]interface{}, error) {
|
||||
podJSON, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(podJSON, &podMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return podMap, nil
|
||||
}
|
||||
|
||||
// SerializeContainer converts a typed Container into a map[string]interface{}
|
||||
func SerializeContainer(container *corev1.Container) (map[string]interface{}, error) {
|
||||
containerJSON, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containerMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(containerJSON, &containerMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containerMap, nil
|
||||
}
|
||||
|
||||
+77
-16
@@ -1,3 +1,17 @@
|
||||
// Copyright 2022 FairwindsOps, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package kube
|
||||
|
||||
import (
|
||||
@@ -60,7 +74,7 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
|
||||
|
||||
func TestGetMultipleResourceFromBadFile(t *testing.T) {
|
||||
_, err := CreateResourceProviderFromPath("./test_files/test_3")
|
||||
assert.NotEqual(t, nil, err, "CreateResource From Path should fail with bad yaml")
|
||||
assert.Equal(t, nil, err, "CreateResource From Path should not fail with bad yaml")
|
||||
}
|
||||
|
||||
func TestAddResourcesFromReader(t *testing.T) {
|
||||
@@ -83,15 +97,6 @@ func TestAddResourcesFromReader(t *testing.T) {
|
||||
|
||||
func TestGetResourceFromAPI(t *testing.T) {
|
||||
k8s, dynamicInterface := test.SetupTestAPI(test.GetMockControllers("test")...)
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface, conf.Configuration{})
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
|
||||
assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path")
|
||||
assert.Equal(t, "test", resources.SourceName, "Should have source name")
|
||||
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")
|
||||
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
|
||||
|
||||
expectedNames := map[string]bool{
|
||||
"deploy": false,
|
||||
@@ -100,12 +105,68 @@ func TestGetResourceFromAPI(t *testing.T) {
|
||||
"statefulset": false,
|
||||
"daemonset": false,
|
||||
}
|
||||
for _, controllers := range resources.Resources {
|
||||
for _, ctrl := range controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config conf.Configuration
|
||||
want *ResourceProvider
|
||||
wantErr bool
|
||||
clusterName string
|
||||
}{
|
||||
{
|
||||
name: "standard",
|
||||
config: conf.Configuration{},
|
||||
clusterName: "test1",
|
||||
want: &ResourceProvider{
|
||||
SourceType: "Cluster",
|
||||
SourceName: "test1",
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespaced",
|
||||
config: conf.Configuration{
|
||||
Namespace: "test",
|
||||
},
|
||||
clusterName: "test2",
|
||||
want: &ResourceProvider{
|
||||
SourceType: "ClusterNamespace",
|
||||
SourceName: "test2",
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not exist",
|
||||
config: conf.Configuration{
|
||||
Namespace: "test3",
|
||||
},
|
||||
clusterName: "test3",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, val := range expectedNames {
|
||||
assert.Equal(t, true, val, name)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, tt.clusterName, &dynamicInterface, tt.config)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want.SourceType, resources.SourceType)
|
||||
assert.Equal(t, tt.want.SourceName, resources.SourceName)
|
||||
assert.IsType(t, tt.want.CreationTime, resources.CreationTime)
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
|
||||
|
||||
for _, controllers := range resources.Resources {
|
||||
for _, ctrl := range controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
}
|
||||
}
|
||||
for name, val := range expectedNames {
|
||||
assert.Equal(t, true, val, name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
package mutation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
strTag = "!!str"
|
||||
seqTag = "!!seq"
|
||||
mapTag = "!!map"
|
||||
intTag = "!!int"
|
||||
boolTag = "!!bool"
|
||||
)
|
||||
|
||||
// ApplyAllMutations applies available mutation to a single resource
|
||||
func ApplyAllMutations(manifest string, mutations []config.Mutation) (string, error) {
|
||||
var mutated string
|
||||
var doc yaml.Node
|
||||
err := yaml.Unmarshal([]byte(manifest), &doc)
|
||||
if err != nil {
|
||||
return mutated, err
|
||||
}
|
||||
|
||||
for _, patch := range mutations {
|
||||
splits := getSplitFromPath(patch.Path)
|
||||
valueNode, err := getNodeFromValue(patch.Value, patch.Comment)
|
||||
if err != nil {
|
||||
return mutated, err
|
||||
}
|
||||
switch patch.Op {
|
||||
case "add", "replace":
|
||||
err = addOrReplaceValue(&doc, splits, valueNode)
|
||||
if err != nil {
|
||||
return mutated, err
|
||||
}
|
||||
case "remove":
|
||||
// ignore error if the value specified does not exists
|
||||
_ = removeNodes(&doc, splits)
|
||||
}
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := yaml.NewEncoder(&buf)
|
||||
enc.SetIndent(2)
|
||||
err = enc.Encode(&doc)
|
||||
if err != nil {
|
||||
return mutated, err
|
||||
}
|
||||
err = enc.Close()
|
||||
if err != nil {
|
||||
return mutated, err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// GetMutationsFromResults returns all mutations from results
|
||||
func GetMutationsFromResults(results []validator.Result) map[string][]config.Mutation {
|
||||
allMutationsFromResults := make(map[string][]config.Mutation)
|
||||
for _, result := range results {
|
||||
key := fmt.Sprintf("%s/%s/%s", result.Kind, result.Name, result.Namespace)
|
||||
mutations := GetMutationsFromResult(&result)
|
||||
allMutationsFromResults[key] = mutations
|
||||
}
|
||||
return allMutationsFromResults
|
||||
}
|
||||
|
||||
// GetMutationsFromResult returns all mutations from single result
|
||||
func GetMutationsFromResult(result *validator.Result) []config.Mutation {
|
||||
mutations := []config.Mutation{}
|
||||
for _, resultMessage := range result.Results {
|
||||
if len(resultMessage.Mutations) > 0 {
|
||||
mutations = append(mutations, resultMessage.Mutations...)
|
||||
}
|
||||
}
|
||||
|
||||
if result.PodResult != nil {
|
||||
for _, resultMessage := range result.PodResult.Results {
|
||||
if len(resultMessage.Mutations) > 0 {
|
||||
mutations = append(mutations, resultMessage.Mutations...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerResult := range result.PodResult.ContainerResults {
|
||||
for _, resultMessage := range containerResult.Results {
|
||||
if len(resultMessage.Mutations) > 0 {
|
||||
mutations = append(mutations, resultMessage.Mutations...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mutations
|
||||
}
|
||||
|
||||
func createPathAndFindNodes(node *yaml.Node, selectors []string, create bool) ([]*yaml.Node, error) {
|
||||
var nodes []*yaml.Node
|
||||
currentSelector := selectors[0]
|
||||
isLastSelector := len(selectors) == 1
|
||||
// array[N] or array[*] selectors.
|
||||
if i := strings.LastIndex(currentSelector, "["); i > 0 && strings.HasSuffix(currentSelector, "]") {
|
||||
arrayIndex := currentSelector[i+1 : len(currentSelector)-1]
|
||||
currentSelector = currentSelector[:i]
|
||||
if checkIfNodeExistedInContent(node.Content, currentSelector) || !create {
|
||||
return findArrayNodes(selectors, currentSelector, node, nodes, arrayIndex, create)
|
||||
}
|
||||
index, err := strconv.Atoi(arrayIndex)
|
||||
if err != nil {
|
||||
if arrayIndex != "-" {
|
||||
return nil, errors.Wrapf(err, "can't parse array index from %v/%v/", currentSelector, arrayIndex)
|
||||
}
|
||||
// if index provided is greater than or less than 0 for an empty array should throw an exception
|
||||
} else if index != 0 {
|
||||
return nil, errors.Errorf("array index (%s) does not exists because array (%s) does not exists", arrayIndex, currentSelector)
|
||||
}
|
||||
// default to zero since no node is present.
|
||||
selectorsToCreateNodes := []string{currentSelector, "0"}
|
||||
if len(selectors) > 1 {
|
||||
selectorsToCreateNodes = append(selectorsToCreateNodes, selectors[1:]...)
|
||||
return createNonExistingPath(selectorsToCreateNodes, node), nil
|
||||
}
|
||||
}
|
||||
if currentSelector == "-" {
|
||||
if isLastSelector {
|
||||
return []*yaml.Node{node}, nil
|
||||
}
|
||||
_, err := createPathAndFindNodes(node, selectors[1:], create)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
// Does the current key match the selector?
|
||||
if node.Content[i].Value == currentSelector {
|
||||
// Found last key, return its value.
|
||||
if !isLastSelector {
|
||||
// Match the rest of the selector path, ie. go deeper
|
||||
// in to the value node.
|
||||
return createPathAndFindNodes(node.Content[i+1], selectors[1:], create)
|
||||
}
|
||||
return []*yaml.Node{node.Content[i+1]}, nil
|
||||
}
|
||||
}
|
||||
case yaml.ScalarNode:
|
||||
// Overwrite any existing nodes.
|
||||
node.Kind = yaml.MappingNode
|
||||
node.Tag = mapTag
|
||||
node.Value = ""
|
||||
case yaml.SequenceNode:
|
||||
return nil, errors.Errorf("parent node is array, use /*/ or /0/../%v/ instead of .%v to access its item(s) first", len(node.Content)-1, currentSelector)
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("parent node is of unknown kind %v", node.Kind)
|
||||
}
|
||||
if !create {
|
||||
return nil, errors.Errorf("failed to match %s", strings.Join(selectors, "/"))
|
||||
}
|
||||
|
||||
return createNonExistingPath(selectors, node), nil
|
||||
}
|
||||
|
||||
func addOrReplaceValue(node *yaml.Node, splits []string, value *yaml.Node) error {
|
||||
if len(node.Content) == 0 {
|
||||
return errors.New("No content in node")
|
||||
}
|
||||
nodes, err := createPathAndFindNodes(node.Content[0], splits, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.Kind == yaml.ScalarNode {
|
||||
// Overwrite an existing scalar value with a new value (whatever kind).
|
||||
*node = *value
|
||||
} else if node.Kind == yaml.MappingNode && value.Kind == yaml.MappingNode {
|
||||
// Append new values onto an existing map node.
|
||||
if len(value.Content) < 2 {
|
||||
logrus.Error("Found a mapping node without enough content")
|
||||
continue
|
||||
}
|
||||
mapKey := value.Content[0].Value
|
||||
found := false
|
||||
for idx, subNode := range node.Content {
|
||||
if idx%2 != 0 {
|
||||
continue
|
||||
}
|
||||
if subNode.Value == mapKey {
|
||||
found = true
|
||||
node.Content[idx+1] = value.Content[1]
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
node.Content = append(value.Content, node.Content...)
|
||||
}
|
||||
} else if node.Kind == yaml.MappingNode && node.Content == nil {
|
||||
// Overwrite a new map node we created in createPathAndFindNodes(), as confirmed
|
||||
// by the nil check (the node.Content wouldn't be nil otherwise).
|
||||
*node = *value
|
||||
} else if node.Kind == yaml.SequenceNode && value.Kind == yaml.SequenceNode {
|
||||
// Append new values onto an existing array node.
|
||||
node.Content = append(node.Content, value.Content...)
|
||||
} else if node.Kind == yaml.SequenceNode && value.Kind == yaml.ScalarNode {
|
||||
// Append new value onto an existing array node.
|
||||
node.Content = append(node.Content, value)
|
||||
} else {
|
||||
return errors.Errorf("can't overwrite %v value (line: %v, column: %v) with %v value", node.Tag, node.Line, node.Column, value.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNodeFromValue(value interface{}, comment string) (*yaml.Node, error) {
|
||||
bytes, err := yaml.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var doc yaml.Node
|
||||
err = yaml.Unmarshal(bytes, &doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(doc.Content) == 0 {
|
||||
return nil, errors.New("Generated an empty YAML document")
|
||||
}
|
||||
if doc.Content[0].Kind == yaml.MappingNode {
|
||||
doc.Content[0].Content[0].HeadComment = comment
|
||||
} else {
|
||||
doc.Content[0].LineComment = comment
|
||||
}
|
||||
return doc.Content[0], nil
|
||||
}
|
||||
|
||||
func removeNodes(doc *yaml.Node, selectors []string) error {
|
||||
err := removeMatchingNode(doc.Content[0], selectors)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to match %q", strings.Join(selectors, "/"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeMatchingNode(node *yaml.Node, selectors []string) error {
|
||||
currentSelector := selectors[0]
|
||||
lastSelector := len(selectors) == 1
|
||||
if i := strings.LastIndex(currentSelector, "["); i > 0 && strings.HasSuffix(currentSelector, "]") {
|
||||
arrayIndex := currentSelector[i+1 : len(currentSelector)-1]
|
||||
currentSelector = currentSelector[:i]
|
||||
|
||||
index, err := strconv.Atoi(arrayIndex)
|
||||
if err != nil {
|
||||
if arrayIndex == "*" {
|
||||
index = -1
|
||||
} else {
|
||||
return errors.Wrapf(err, "can't parse array index from %v/%v/", currentSelector, arrayIndex)
|
||||
}
|
||||
} else if index < 0 {
|
||||
return errors.Wrapf(err, "array index can't be negative %v/%v/", currentSelector, arrayIndex)
|
||||
}
|
||||
// Go into array node(s).
|
||||
arrayNodes, err := createPathAndFindNodes(node, []string{currentSelector}, false)
|
||||
if err != nil {
|
||||
return errors.Errorf("can't find %v", currentSelector)
|
||||
}
|
||||
for _, arrayNode := range arrayNodes {
|
||||
if arrayNode.Kind != yaml.SequenceNode {
|
||||
return errors.Errorf("%v is not an array", currentSelector)
|
||||
}
|
||||
if index >= len(arrayNode.Content) {
|
||||
return errors.Errorf("%v array doesn't have index %v", currentSelector, index)
|
||||
}
|
||||
|
||||
var visitArrayNodes []*yaml.Node
|
||||
if index >= 0 { // array[N]
|
||||
visitArrayNodes = []*yaml.Node{arrayNode.Content[index]}
|
||||
} else { // array[*]
|
||||
visitArrayNodes = arrayNode.Content
|
||||
}
|
||||
for _, node := range visitArrayNodes {
|
||||
lastSelector := len(selectors) == 1
|
||||
if !lastSelector {
|
||||
removeMatchingNode(node, selectors[1:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the keys (the slice is key/value pairs).
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
if node.Content[i].Value == currentSelector {
|
||||
// Key matches the selector.
|
||||
if !lastSelector {
|
||||
// Try to match the rest of the selector path in the value.
|
||||
return removeMatchingNode(node.Content[i+1], selectors[1:])
|
||||
}
|
||||
|
||||
node.Content[i] = nil // Delete key.
|
||||
node.Content[i+1] = nil // Delete value.
|
||||
node.Content = append(node.Content[:i], node.Content[i+2:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Errorf("can't find %q", strings.Join(selectors, "."))
|
||||
}
|
||||
|
||||
func getSplitFromPath(path string) []string {
|
||||
var digitStarCheck = regexp.MustCompile(`^[0-9*]+$`)
|
||||
splits := strings.Split(path, "/")
|
||||
var formatedSplit []string
|
||||
for _, key := range splits {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if digitStarCheck.MatchString(key) {
|
||||
lastElementIdx := len(formatedSplit) - 1
|
||||
lastElement := formatedSplit[lastElementIdx]
|
||||
lastElement = fmt.Sprintf("%s[%s]", lastElement, key)
|
||||
formatedSplit[lastElementIdx] = lastElement
|
||||
continue
|
||||
}
|
||||
formatedSplit = append(formatedSplit, key)
|
||||
}
|
||||
return formatedSplit
|
||||
}
|
||||
|
||||
func findArrayNodes(selectors []string, currentSelector string, node *yaml.Node, nodes []*yaml.Node, arrayIndex string, create bool) ([]*yaml.Node, error) {
|
||||
|
||||
index, err := strconv.Atoi(arrayIndex)
|
||||
if err != nil {
|
||||
if arrayIndex == "*" {
|
||||
index = -1
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "can't parse array index from %v/%v/", currentSelector, arrayIndex)
|
||||
}
|
||||
} else if index < 0 {
|
||||
return nil, errors.Wrapf(err, "array index can't be negative %v/%v/", currentSelector, arrayIndex)
|
||||
}
|
||||
// Go into array node(s).
|
||||
arrayNodes, err := createPathAndFindNodes(node, []string{currentSelector}, create)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("can't find %v", currentSelector)
|
||||
}
|
||||
for _, arrayNode := range arrayNodes {
|
||||
if arrayNode.Kind != yaml.SequenceNode {
|
||||
return nil, errors.Errorf("%v is not an array", currentSelector)
|
||||
}
|
||||
if index >= len(arrayNode.Content) {
|
||||
return nil, errors.Errorf("%v array doesn't have index %v", currentSelector, index)
|
||||
}
|
||||
|
||||
var visitArrayNodes []*yaml.Node
|
||||
if index >= 0 { // array[N]
|
||||
visitArrayNodes = []*yaml.Node{arrayNode.Content[index]}
|
||||
} else { // array[*]
|
||||
visitArrayNodes = arrayNode.Content
|
||||
}
|
||||
|
||||
for i, node := range visitArrayNodes {
|
||||
if len(selectors) == 1 {
|
||||
// Last selector, use this as final node.
|
||||
nodes = append(nodes, node)
|
||||
} else {
|
||||
// Go deeper into a specific array.
|
||||
deeperNodes, err := createPathAndFindNodes(node, selectors[1:], create)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to go deeper into %v/%v/", currentSelector, i)
|
||||
}
|
||||
nodes = append(nodes, deeperNodes...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func checkIfNodeExistedInContent(nodes []*yaml.Node, currentSelector string) bool {
|
||||
for i := 0; i < len(nodes); i += 2 {
|
||||
// Does the current key match the selector?
|
||||
if nodes[i].Value == currentSelector {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func createNonExistingPath(selectors []string, node *yaml.Node) []*yaml.Node {
|
||||
var digitDashCheck = regexp.MustCompile(`^[0-9-]+$`)
|
||||
// Create the rest of the selector path.
|
||||
for idx, selector := range selectors {
|
||||
if digitDashCheck.MatchString(selector) {
|
||||
continue
|
||||
}
|
||||
kind, tag := yaml.MappingNode, mapTag
|
||||
// if the next selector is "-" then current selector is sequence/slice/array
|
||||
if idx < len(selectors)-1 && digitDashCheck.MatchString(selectors[idx+1]) {
|
||||
kind, tag = yaml.SequenceNode, seqTag
|
||||
}
|
||||
var newNode = yaml.Node{
|
||||
Content: []*yaml.Node{
|
||||
{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: strTag,
|
||||
Value: selector,
|
||||
},
|
||||
{
|
||||
Kind: kind,
|
||||
Tag: tag,
|
||||
},
|
||||
},
|
||||
}
|
||||
// if previous node is array/sequenceNode append a node rather than appending contents
|
||||
if node.Kind == yaml.SequenceNode {
|
||||
newNode.Kind = kind
|
||||
node.Content = append(node.Content, &newNode)
|
||||
} else {
|
||||
node.Content = append(node.Content, newNode.Content...)
|
||||
}
|
||||
node = newNode.Content[len(newNode.Content)-1]
|
||||
}
|
||||
return []*yaml.Node{node}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package mutation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
)
|
||||
|
||||
var oldYaml = `
|
||||
pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
- name: Bob
|
||||
aliases:
|
||||
- Robert
|
||||
- name: scooby
|
||||
`
|
||||
|
||||
var testCases = []struct {
|
||||
original string
|
||||
mutated string
|
||||
patch config.Mutation
|
||||
message string
|
||||
}{{
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "Denver",
|
||||
Path: "/pets/0/owners/*/location",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
location: Denver
|
||||
- name: Bob
|
||||
aliases:
|
||||
- Robert
|
||||
location: Denver
|
||||
- name: scooby
|
||||
`,
|
||||
}, {
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "remove",
|
||||
Path: "/pets/0/owners/*/aliases",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
- name: Bob
|
||||
- name: scooby
|
||||
`,
|
||||
}, {
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "rob",
|
||||
Path: "/pets/0/owners/*/aliases/-",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
aliases:
|
||||
- rob
|
||||
- name: Bob
|
||||
aliases:
|
||||
- Robert
|
||||
- rob
|
||||
- name: scooby
|
||||
`,
|
||||
}, {
|
||||
original: `
|
||||
pets:
|
||||
- name: fido
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "Alice",
|
||||
Path: "/pets/0/owners/0/name",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
`,
|
||||
}, {
|
||||
original: `
|
||||
obj:
|
||||
foo:
|
||||
bar:
|
||||
- a
|
||||
- b
|
||||
baz: quux
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: map[string]interface{}{
|
||||
"bar": []string{"c", "d"},
|
||||
},
|
||||
Path: "/obj/foo",
|
||||
},
|
||||
mutated: `obj:
|
||||
foo:
|
||||
bar:
|
||||
- c
|
||||
- d
|
||||
baz: quux
|
||||
`,
|
||||
}, {
|
||||
original: `
|
||||
foo: bar
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: "baz",
|
||||
Path: "/foo",
|
||||
Comment: "# We set this to baz",
|
||||
},
|
||||
mutated: `
|
||||
foo: baz # We set this to baz
|
||||
`,
|
||||
message: "Expected a comment to appear",
|
||||
}, {
|
||||
original: `
|
||||
foo: bar
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: map[string]interface{}{
|
||||
"baz": "quux",
|
||||
},
|
||||
Path: "/extra",
|
||||
Comment: "# These are extra things",
|
||||
},
|
||||
mutated: `
|
||||
foo: bar
|
||||
extra:
|
||||
# These are extra things
|
||||
baz: quux
|
||||
`,
|
||||
message: "Expected a comment to appear next to an object",
|
||||
}}
|
||||
|
||||
func TestApplyAllMutations(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
mutated, err := ApplyAllMutations(tc.original, []config.Mutation{tc.patch})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, strings.TrimSpace(tc.mutated), strings.TrimSpace(mutated), tc.message)
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func TestValidatePDB(t *testing.T) {
|
||||
},
|
||||
}
|
||||
pdb := unstructured.Unstructured{}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&pdb)
|
||||
res, err := kube.NewGenericResourceFromUnstructured(pdb, nil)
|
||||
res.Kind = "PodDisruptionBudget"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
@@ -45,7 +45,7 @@ func TestValidatePDB(t *testing.T) {
|
||||
assert.False(t, results.Success)
|
||||
assert.Equal(t, conf.SeverityWarning, results.Severity)
|
||||
assert.Equal(t, "Reliability", results.Category)
|
||||
assert.EqualValues(t, "disruptionsAllowed is not greater than zero", results.Message)
|
||||
assert.EqualValues(t, "Voluntary evictions are not possible", results.Message)
|
||||
}
|
||||
|
||||
func TestValidateIngress(t *testing.T) {
|
||||
@@ -70,7 +70,7 @@ func TestValidateIngress(t *testing.T) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&unst)
|
||||
res, err := kube.NewGenericResourceFromUnstructured(unst, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user