Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
195619fcab | ||
|
|
9ae4f774e9 | ||
|
|
6c14d968c9 | ||
|
|
3e655d846b | ||
|
|
0232e31a50 | ||
|
|
a396f049d0 | ||
|
|
a55acdd372 | ||
|
|
ca3e46ebbd | ||
|
|
e661e9542d | ||
|
|
b923caf79e | ||
|
|
3e79863aa7 | ||
|
|
d172d8e18b | ||
|
|
38c410c5aa | ||
|
|
653eac6002 | ||
|
|
6c995eb04c | ||
|
|
e52f1e76e9 | ||
|
|
cf69099d3a | ||
|
|
20f1be3975 | ||
|
|
f602687c90 | ||
|
|
a3404a132d | ||
|
|
46836ccc4c | ||
|
|
ff8e66af07 | ||
|
|
01cc5fa642 | ||
|
|
cd47487b0b | ||
|
|
7e7e553c0d | ||
|
|
8385fd10e5 | ||
|
|
ccc795d58e | ||
|
|
0007b12080 | ||
|
|
2958b5cc07 | ||
|
|
0c5014489e | ||
|
|
ebf99869ff | ||
|
|
a43a0fe2f5 | ||
|
|
88b57f9f32 | ||
|
|
e028c34448 | ||
|
|
7c1ec6a762 | ||
|
|
fa6843ae61 | ||
|
|
76cc9e3296 | ||
|
|
6bcdb834ed | ||
|
|
2bac1c9ba9 | ||
|
|
0e6ca81f57 | ||
|
|
4bc2522e25 | ||
|
|
d4bb6239c7 | ||
|
|
082e9c0406 | ||
|
|
178742e0f3 | ||
|
|
1651aa7004 | ||
|
|
e6c9e4558e | ||
|
|
b6f1bdf098 | ||
|
|
cbc15ad069 | ||
|
|
68d295ec6b | ||
|
|
90339357c2 | ||
|
|
2e7368d011 | ||
|
|
6cd65fbed8 | ||
|
|
1ede736971 | ||
|
|
1935abd563 | ||
|
|
842ccf4853 | ||
|
|
2c56a313a1 | ||
|
|
7b00d73a6f | ||
|
|
a995e7e724 | ||
|
|
3211d6d25d | ||
|
|
d011bb454a | ||
|
|
8e62436697 | ||
|
|
c9811171ce | ||
|
|
30eebaf16a | ||
|
|
dbf001fa53 | ||
|
|
f753fc91f2 | ||
|
|
239a321588 | ||
|
|
61eb1f95a4 | ||
|
|
a66a6b89da | ||
|
|
46923d0a0a | ||
|
|
ba1b4d5db9 | ||
|
|
ba0a25acf9 | ||
|
|
371e30fe3d | ||
|
|
1013834e3c | ||
|
|
e802c28801 | ||
|
|
14d503580b | ||
|
|
0fc109c199 | ||
|
|
63fd576d3e | ||
|
|
f0c8ee256e | ||
|
|
d5cb68084e | ||
|
|
f7d2309608 | ||
|
|
d800e8d629 | ||
|
|
eebc9537a2 | ||
|
|
1fed099b53 | ||
|
|
25dfae1fea | ||
|
|
c365fb08cf | ||
|
|
768b715fdf | ||
|
|
7d938ac405 | ||
|
|
2f5964519a | ||
|
|
2064384985 | ||
|
|
b436699260 | ||
|
|
1a025da649 | ||
|
|
4c3d0e0603 | ||
|
|
714b7bfbba | ||
|
|
16ffe1e1c4 | ||
|
|
7f1c143d77 | ||
|
|
f42af35352 | ||
|
|
8bbe13b6fa | ||
|
|
dea7143827 | ||
|
|
41d5f72822 | ||
|
|
3e49a3af98 | ||
|
|
89ff4a6d53 | ||
|
|
a5852f3003 | ||
|
|
4a04999ce7 | ||
|
|
c16aac808f | ||
|
|
0aa173789a | ||
|
|
38b5cd8193 | ||
|
|
ef59f7c26e | ||
|
|
08900887e4 | ||
|
|
9ddbe5a329 | ||
|
|
3f7fccf5f1 | ||
|
|
d9e148c0f2 | ||
|
|
613c4b9ea1 | ||
|
|
fe0060af77 | ||
|
|
7b428fe230 | ||
|
|
2e148546d8 | ||
|
|
23d76594a6 | ||
|
|
512a5f2886 | ||
|
|
09d5fdcd1e | ||
|
|
e4d6fb9b61 | ||
|
|
c11b839009 | ||
|
|
6cda7cd7d5 | ||
|
|
381c24e940 | ||
|
|
a8e901639e | ||
|
|
93a80e44d3 | ||
|
|
bc866a4d18 | ||
|
|
ec557f7ce8 | ||
|
|
4cee8b7e35 | ||
|
|
ba53a2b8ab | ||
|
|
3a8655de81 | ||
|
|
3f62126bdd | ||
|
|
ee8768591b | ||
|
|
6e93b799b1 | ||
|
|
17d19cacff | ||
|
|
f5a1ad61bd | ||
|
|
8840f0dc5b | ||
|
|
5ab9f0b251 | ||
|
|
a540a7359a | ||
|
|
fc368485ef | ||
|
|
8f510a1b67 | ||
|
|
9d68ee2359 | ||
|
|
ef4f5aeaeb | ||
|
|
a79260a324 | ||
|
|
0c398d21f2 | ||
|
|
f1957631b5 | ||
|
|
86b3ab5186 | ||
|
|
a1c131f922 | ||
|
|
54aacb786f | ||
|
|
564803c9f8 | ||
|
|
f421d7e5a8 | ||
|
|
5f793beb73 | ||
|
|
5fd9e01493 | ||
|
|
a4e45a0e95 | ||
|
|
dd2976794a | ||
|
|
edf28c8790 | ||
|
|
7c98598858 | ||
|
|
2393f0bbf9 | ||
|
|
4e044602f4 | ||
|
|
fdd30717e5 | ||
|
|
272e06bbec | ||
|
|
e57668fc75 | ||
|
|
9dd7f0947a | ||
|
|
ca6e4b43e4 | ||
|
|
3a2fb3584b | ||
|
|
550447234a | ||
|
|
717d9b2630 | ||
|
|
6b7ccdadc4 | ||
|
|
b511abab60 | ||
|
|
fab5cb9a21 | ||
|
|
949eb9e04a | ||
|
|
e4656bcae8 | ||
|
|
f62454df8d | ||
|
|
7374d8b5dd | ||
|
|
99b9c0af27 | ||
|
|
fbff67c7eb | ||
|
|
6fc1ee0adb | ||
|
|
2bf5a195c1 | ||
|
|
2d326419c2 | ||
|
|
31fc0ab6d9 | ||
|
|
7e2bd017b3 | ||
|
|
5d6446afed | ||
|
|
b1ead662a2 | ||
|
|
bf9173acea | ||
|
|
568a79b4f6 | ||
|
|
e64fb4cdbd | ||
|
|
2b77ac2b1e | ||
|
|
20ea63ebe5 | ||
|
|
11d5200962 | ||
|
|
aefad52704 | ||
|
|
86b856a88c | ||
|
|
33dcb3f267 | ||
|
|
7c9ab1395f | ||
|
|
1a8fb44b78 | ||
|
|
51d91c7d2e | ||
|
|
0cd0d83172 | ||
|
|
b3f1b3b478 | ||
|
|
4ff34de49b | ||
|
|
5bce1db05e | ||
|
|
b27f619d5e |
@@ -17,6 +17,7 @@ references:
|
||||
echo 'export PUSH_ALL_VERSION_TAGS=true' >> ${BASH_ENV}
|
||||
echo 'export GOPROXY=https://proxy.golang.org' >> ${BASH_ENV}
|
||||
echo 'export GO111MODULE=on' >> ${BASH_ENV}
|
||||
echo 'export GOFLAGS=-mod=mod' >> ${BASH_ENV}
|
||||
|
||||
install_k8s: &install_k8s
|
||||
run:
|
||||
@@ -45,16 +46,19 @@ references:
|
||||
|
||||
kubectl version
|
||||
|
||||
# Test scripts
|
||||
update_coverage: &update_coverage
|
||||
run:
|
||||
name: Update Coverage
|
||||
command: |
|
||||
if [[ -z $CIRCLE_PR_NUMBER ]]; then
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
else
|
||||
echo "Skipping coverage for forked PR"
|
||||
fi
|
||||
echo "Installing Helm"
|
||||
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
|
||||
chmod 700 get_helm.sh
|
||||
./get_helm.sh
|
||||
|
||||
echo "Installing cert-manager"
|
||||
kubectl create namespace cert-manager
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
helm repo update
|
||||
echo "Helm install"
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
|
||||
echo "Install cert-manager successful"
|
||||
|
||||
test_binary_dashboard: &test_binary_dashboard
|
||||
run:
|
||||
name: Test Dashboard
|
||||
@@ -144,22 +148,36 @@ jobs:
|
||||
test:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
- image: circleci/golang:1.16
|
||||
steps:
|
||||
- checkout
|
||||
- *set_environment_variables
|
||||
- run: go get -u golang.org/x/lint/golint
|
||||
- run: go list ./... | grep -v vendor | xargs golint -set_exit_status
|
||||
- run: go list ./... | grep -v vendor | xargs go vet
|
||||
- run: go test ./pkg/... -coverprofile=coverage.txt -covermode=count
|
||||
- run: go test ./... -coverprofile=coverage.txt -covermode=count
|
||||
- run: go run main.go audit --audit-path ./deploy --set-exit-code-below-score 100 --set-exit-code-on-danger
|
||||
- *update_coverage
|
||||
- *test_binary_dashboard
|
||||
|
||||
insights:
|
||||
docker:
|
||||
- image: quay.io/reactiveops/ci-images:v11.0-stretch
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Adjust configs for latest image
|
||||
command: |
|
||||
sed -r "s|'(quay.io/fairwinds/polaris:).+'|'\1${CIRCLE_SHA1}'|" ./deploy/webhook.yaml > ./deploy/dashboard.yaml
|
||||
sed -r "s|'(quay.io/fairwinds/polaris:).+'|'\1${CIRCLE_SHA1}'|" ./deploy/dashboard.yaml > ./deploy/webhook.yaml
|
||||
- run:
|
||||
name: Insights CI
|
||||
command: curl -L https://insights.fairwinds.com/v0/insights-ci.sh | bash
|
||||
|
||||
release_binary:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
- image: circleci/golang:1.16
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
@@ -181,6 +199,31 @@ jobs:
|
||||
- *set_environment_variables
|
||||
- *docker_build_and_push
|
||||
|
||||
publish_docs:
|
||||
docker:
|
||||
- image: cimg/node:15.5.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Build Docs Site
|
||||
command: |
|
||||
set -e
|
||||
cd ./docs
|
||||
npm install
|
||||
npm run check-links
|
||||
npm run build
|
||||
- run:
|
||||
name: Install AWS CLI
|
||||
command: |
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
- run:
|
||||
name: Publish Docs Site to S3
|
||||
command: |
|
||||
cd ./dist
|
||||
aws s3 sync ./ s3://polaris.docs.fairwinds.com --delete
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
@@ -197,6 +240,12 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
- insights:
|
||||
requires:
|
||||
- push
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
- test_k8s:
|
||||
requires:
|
||||
- push
|
||||
@@ -211,7 +260,6 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# Testing tags are reserved for testing circle test + build steps
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- release_images:
|
||||
@@ -221,6 +269,11 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# Testing tags are reserved for testing circle test + build steps
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- publish_docs:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# The action uses an own Dockerfile on purpose because the root Dockerfile takes way too long to build for an action
|
||||
|
||||
FROM alpine:3.10
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
wget \
|
||||
tar \
|
||||
jq
|
||||
|
||||
COPY get_polaris.sh /get_polaris.sh
|
||||
|
||||
ENTRYPOINT ["/get_polaris.sh"]
|
||||
@@ -0,0 +1,22 @@
|
||||
name: 'Install polaris'
|
||||
description: 'Download a specific polaris version'
|
||||
|
||||
inputs:
|
||||
version:
|
||||
description: 'version of polaris'
|
||||
required: true
|
||||
default: 'latest'
|
||||
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: './Dockerfile'
|
||||
args:
|
||||
- ${{ inputs.version }}
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: 'Version of polaris installed'
|
||||
|
||||
branding:
|
||||
icon: 'download-cloud'
|
||||
color: 'gray-dark'
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
if [[ -z "$INPUT_VERSION" ]]; then
|
||||
echo "Missing polaris version information"
|
||||
exit 1
|
||||
fi
|
||||
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."
|
||||
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'
|
||||
mkdir polaris
|
||||
tar -xzf $TARGET_FILE -C polaris
|
||||
rm $TARGET_FILE
|
||||
echo "polaris" >> $GITHUB_PATH
|
||||
echo "::set-output name=version::$INPUT_VERSION"
|
||||
@@ -0,0 +1,44 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: "11:00"
|
||||
ignore:
|
||||
- dependency-name: cloud.google.com/go
|
||||
versions:
|
||||
- ">= 0.57.a, < 0.58"
|
||||
- dependency-name: github.com/go-logr/logr
|
||||
versions:
|
||||
- ">= 0.2.a, < 0.3"
|
||||
- dependency-name: github.com/go-logr/zapr
|
||||
versions:
|
||||
- ">= 0.2.a, < 0.3"
|
||||
- dependency-name: github.com/googleapis/gnostic
|
||||
versions:
|
||||
- ">= 0.4.a, < 0.5"
|
||||
- dependency-name: github.com/googleapis/gnostic
|
||||
versions:
|
||||
- ">= 0.5.a, < 0.6"
|
||||
- dependency-name: github.com/qri-io/jsonschema
|
||||
versions:
|
||||
- ">= 0.2.a, < 0.3"
|
||||
- dependency-name: k8s.io/api
|
||||
versions:
|
||||
- ">= 0.19.a, < 0.20"
|
||||
- dependency-name: k8s.io/apimachinery
|
||||
versions:
|
||||
- ">= 0.19.a, < 0.20"
|
||||
- dependency-name: k8s.io/client-go
|
||||
versions:
|
||||
- ">= 0.19.a, < 0.20"
|
||||
- dependency-name: sigs.k8s.io/controller-runtime
|
||||
versions:
|
||||
- ">= 0.5.a, < 0.6"
|
||||
- dependency-name: sigs.k8s.io/controller-runtime
|
||||
versions:
|
||||
- ">= 0.8.a, < 0.9"
|
||||
- dependency-name: k8s.io/apimachinery
|
||||
versions:
|
||||
- 0.20.4
|
||||
@@ -0,0 +1,18 @@
|
||||
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,27 @@
|
||||
name: Test setup-polaris
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build-int:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup polaris
|
||||
uses: ./.github/actions/setup-polaris
|
||||
with:
|
||||
version: 3.0.3
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
|
||||
build-ext:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup polaris
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
with:
|
||||
version: 3.0.3
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
@@ -24,3 +24,6 @@ dist
|
||||
.vscode
|
||||
|
||||
*-test.yaml
|
||||
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
@@ -7,7 +7,7 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
builds:
|
||||
- id: watcher
|
||||
- id: polaris
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
@@ -15,10 +15,13 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- 386
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
archives:
|
||||
- id: polaris
|
||||
builds: ["polaris"]
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
||||
brews:
|
||||
- name: polaris
|
||||
github:
|
||||
|
||||
@@ -1 +1 @@
|
||||
* @rbren @makoscafee @jordandoig @baderbuddy
|
||||
* @rbren @makoscafee
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.13 AS build-env
|
||||
FROM golang:1.16 AS build-env
|
||||
WORKDIR /go/src/github.com/fairwindsops/polaris/
|
||||
|
||||
ENV GO111MODULE=on
|
||||
@@ -15,7 +15,7 @@ RUN go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
COPY . .
|
||||
RUN packr2 build -a -o polaris *.go
|
||||
|
||||
FROM alpine:3.10
|
||||
FROM alpine:3.14
|
||||
WORKDIR /usr/local/bin
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
|
||||
@@ -1,84 +1,58 @@
|
||||
<div align="center">
|
||||
<img src="/img/polaris-logo.png" alt="Polaris Logo" />
|
||||
<div align="center" class="no-border">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/polaris-logo.png" alt="Polaris Logo">
|
||||
<br>
|
||||
|
||||
[![Version][version-image]][version-link] [![CircleCI][circleci-image]][circleci-link] [![Go Report Card][goreport-image]][goreport-link]
|
||||
<h3>Best Practices for Kubernetes Workload Configuration</h3>
|
||||
<a href="https://github.com/FairwindsOps/polaris">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=4.0.8&color=239922">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
|
||||
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
|
||||
</a>
|
||||
<a href="https://circleci.com/gh/FairwindsOps/polaris">
|
||||
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
|
||||
</a>
|
||||
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
|
||||
<img src="https://insights.fairwinds.com/v0/gh/FairwindsOps/polaris/badge.svg">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
[version-image]: https://img.shields.io/static/v1.svg?label=Version&message=1.2.0&color=239922
|
||||
[version-link]: https://github.com/FairwindsOps/polaris
|
||||
|
||||
[goreport-image]: https://goreportcard.com/badge/github.com/FairwindsOps/polaris
|
||||
[goreport-link]: https://goreportcard.com/report/github.com/FairwindsOps/polaris
|
||||
|
||||
[circleci-image]: https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg
|
||||
[circleci-link]: https://circleci.com/gh/FairwindsOps/polaris.svg
|
||||
|
||||
Fairwinds' Polaris keeps your clusters sailing smoothly. It runs a variety of checks to ensure that
|
||||
Kubernetes pods and controllers are configured using best practices, helping you avoid
|
||||
problems in the future. Polaris can be run in a few different modes:
|
||||
problems in the future.
|
||||
|
||||
Polaris can be run in three different modes:
|
||||
* As a [dashboard](#dashboard), so you can audit what's running inside your cluster.
|
||||
* As a [validating webhook](#webhook), so you can automatically reject workloads that don't adhere to your organization's policies.
|
||||
* As a [command-line tool](#cli), so you can test local YAML files, e.g. as part of a CI/CD process.
|
||||
|
||||
**Want to learn more?** Reach out on [the Slack channel](https://fairwindscommunity.slack.com/messages/polaris) ([request invite](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)), send an email to `opensource@fairwinds.com`, or join us for [office hours on Zoom](https://fairwindscommunity.slack.com/messages/office-hours)
|
||||
|
||||
# Dashboard Quickstart
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://github.com/FairwindsOps/polaris/releases/latest/download/dashboard.yaml
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
With the port forwarding in place, you can open http://localhost:8080 in your browser to view the dashboard.
|
||||
|
||||
* * *
|
||||
|
||||
# Components
|
||||
|
||||
## Dashboard
|
||||
> [View installation instructions](docs/usage.md#dashboard)
|
||||
|
||||
The Polaris dashboard is a way to get a simple visual overview of the current state of your Kubernetes workloads as well as a roadmap for what can be improved. The dashboard provides a cluster wide overview as well as breaking out results by category, namespace, and workload.
|
||||
* As a [dashboard](https://polaris.docs.fairwinds.com/dashboard), so you can audit what's running inside your cluster.
|
||||
* As an [admission controller](https://polaris.docs.fairwinds.com/admission-controller), so you can automatically reject workloads that don't adhere to your organization's policies.
|
||||
* As a [command-line tool](https://polaris.docs.fairwinds.com/infrastructure-as-code), so you can test local YAML files, e.g. as part of a CI/CD process.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
|
||||
<img src="https://polaris.docs.fairwinds.com/img/architecture.svg" alt="Polaris Architecture" width="550"/>
|
||||
</p>
|
||||
|
||||
Our default standards in Polaris are rather high, so don’t be surprised if your score is lower than you might expect. A key goal for Polaris was to set a high standard and aim for great configuration by default. If the defaults we’ve included are too strict, it’s easy to adjust the configuration as part of the deployment configuration to better suit your workloads.
|
||||
## Documentation
|
||||
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
|
||||
|
||||
## 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!
|
||||
|
||||
|
||||
## Admission Controller: Validating Webhook
|
||||
> [View installation instructions](docs/usage.md#webhook)
|
||||
## Other Projects from Fairwinds
|
||||
|
||||
Polaris can be run as an admission controller that acts as a validating webhook. This accepts the same configuration as the dashboard, and can run the same validations. This webhook will reject any workloads that trigger a danger-level check. This is indicative of the greater goal of Polaris, not just to encourage better configuration through dashboard visibility, but to actually enforce it with this webhook. Polaris will not fix your workloads, only block them.
|
||||
Enjoying Polaris? Check out some of our other projects:
|
||||
* [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage
|
||||
* [Pluto](https://github.com/FairwindsOps/Pluto) - Detect Kubernetes resources that have been deprecated or removed in future versions
|
||||
* [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available
|
||||
* [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters
|
||||
|
||||
The webhook only supports a fixed set of controller types. You can see the
|
||||
[list of supported controllers here](https://github.com/FairwindsOps/polaris/blob/master/cmd/polaris/webhook.go#L40)
|
||||
## Fairwinds Insights
|
||||
<p align="center">
|
||||
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/insights-banner.png" alt="Fairwinds Insights" width="550"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Unfortunately we have not found a way to display warnings as part of `kubectl` output unless we are rejecting a workload altogether. That means that any checks with a severity of `warning` will still pass webhook validation, and the only evidence of that warning will either be in the Polaris dashboard or the Polaris webhook logs.
|
||||
|
||||
## CLI
|
||||
> [View installation instructions](docs/usage.md#cli)
|
||||
|
||||
Polaris can also be used on the command line, either to audit local files or a running cluster.
|
||||
This is particularly helpful for running Polaris against your infrastructure-as-code as part of a
|
||||
CI/CD pipeline. Use the available [command line flags](docs/usage.md#running-with-ci-cd)
|
||||
to cause CI/CD to fail if your Polaris score drops below a certain threshold, or if any danger-level issues arise.
|
||||
|
||||
# Installation and Usage
|
||||
See the [Usage Guide](/docs/usage.md) for details on different methods for installing and using Polaris.
|
||||
|
||||
# Contributing
|
||||
PRs welcome! Check out the [Contributing Guidelines](CONTRIBUTING.md),
|
||||
[Code of Conduct](CODE_OF_CONDUCT.md), and [Roadmap](ROADMAP.md) for more information.
|
||||
|
||||
# Further Information
|
||||
A history of changes to this project can be viewed in the [Changelog](CHANGELOG.md)
|
||||
|
||||
If you'd like to learn more about Polaris, or if you'd like to speak with
|
||||
a Kubernetes expert, you can contact `info@fairwinds.com` or [visit our website](https://fairwinds.com)
|
||||
|
||||
# License
|
||||
Apache License 2.0
|
||||
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.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Polaris Roadmap
|
||||
We plan to continue expanding the list of checks available to Polaris users,
|
||||
as well as the project's overall functionality.
|
||||
|
||||
If you have ideas for a new check, or for new Polaris features,
|
||||
you can submit a pull request for this file, or open an issue in GitHub.
|
||||
|
||||
## Check non-controllers
|
||||
We would like to implement checks for non-controller types, such as:
|
||||
* Ingresses
|
||||
* Services
|
||||
* RBAC roles/bindings
|
||||
|
||||
## Expand list of checks
|
||||
We'd like to increase our library of checks. Not all checks need to be enabled by default,
|
||||
so anything that would be useful across different organizations is worth checking in.
|
||||
|
||||
## Support more controllers in the validating webhook
|
||||
Currently the webhook only checks for a fixed set of controllers.
|
||||
|
||||
While we can't listen for _all_ possible controller types, we can block Pods that violate policy.
|
||||
We've chosen not to do this for now, because it could prevent existing controllers from scaling.
|
||||
|
||||
However, there may be a way to check for the owner's age, in order to determine if it's a new
|
||||
controller or a pre-existing controller.
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: CPU limits are set
|
||||
failureMessage: CPU limits should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: CPU requests are set
|
||||
failureMessage: CPU requests should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -14,12 +14,13 @@ schema:
|
||||
properties:
|
||||
add:
|
||||
type: array
|
||||
not:
|
||||
contains:
|
||||
const: ALL
|
||||
not:
|
||||
contains:
|
||||
const: SYS_ADMIN
|
||||
not:
|
||||
contains:
|
||||
const: NET_ADMIN
|
||||
allOf:
|
||||
- not:
|
||||
contains:
|
||||
const: ALL
|
||||
- not:
|
||||
contains:
|
||||
const: SYS_ADMIN
|
||||
- not:
|
||||
contains:
|
||||
const: NET_ADMIN
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Host network is not configured
|
||||
failureMessage: Host network should not be configured
|
||||
category: Networking
|
||||
category: Security
|
||||
target: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Host port is not configured
|
||||
failureMessage: Host port should not be configured
|
||||
category: Networking
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -5,27 +5,52 @@ target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
required:
|
||||
- capabilities
|
||||
properties:
|
||||
capabilities:
|
||||
type: object
|
||||
required:
|
||||
- drop
|
||||
properties:
|
||||
add:
|
||||
enum:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FSETID
|
||||
- FOWNER
|
||||
- MKNOD
|
||||
- NET_RAW
|
||||
- SETGID
|
||||
- SETUID
|
||||
- SETFCAP
|
||||
- SETPCAP
|
||||
- NET_BIND_SERVICE
|
||||
- SYS_CHROOT
|
||||
- KILL
|
||||
- AUDIT_WRITE
|
||||
|
||||
drop:
|
||||
type: array
|
||||
oneOf:
|
||||
- contains:
|
||||
const: ALL
|
||||
- allOf:
|
||||
- contains:
|
||||
const: NET_ADMIN
|
||||
- contains:
|
||||
const: CHOWN
|
||||
- contains:
|
||||
const: DAC_OVERRIDE
|
||||
- contains:
|
||||
const: FSETID
|
||||
- contains:
|
||||
const: FOWNER
|
||||
- contains:
|
||||
const: MKNOD
|
||||
- contains:
|
||||
const: NET_RAW
|
||||
- contains:
|
||||
const: SETGID
|
||||
- contains:
|
||||
const: SETUID
|
||||
- contains:
|
||||
const: SETFCAP
|
||||
- contains:
|
||||
const: SETPCAP
|
||||
- contains:
|
||||
const: NET_BIND_SERVICE
|
||||
- contains:
|
||||
const: SYS_CHROOT
|
||||
- contains:
|
||||
const: KILL
|
||||
- contains:
|
||||
const: AUDIT_WRITE
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Liveness probe is configured
|
||||
failureMessage: Liveness probe should be configured
|
||||
category: Health Checks
|
||||
category: Reliability
|
||||
controllers:
|
||||
exclude:
|
||||
- Job
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Memory limits are set
|
||||
failureMessage: Memory limits should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Memory requests are set
|
||||
failureMessage: Memory requests should be set
|
||||
category: Resources
|
||||
category: Efficiency
|
||||
target: Container
|
||||
containers:
|
||||
exclude:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
successMessage: Label app.kubernetes.io/name matches metadata.name
|
||||
failureMessage: Label app.kubernetes.io/name must match metadata.name
|
||||
target: Controller
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["labels"]
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
required: ["app.kubernetes.io/name"]
|
||||
properties:
|
||||
app.kubernetes.io/name:
|
||||
const: "{{ .metadata.name }}"
|
||||
@@ -0,0 +1,39 @@
|
||||
successMessage: A PodDisruptionBudget is attached
|
||||
failureMessage: Should have a PodDisruptionBudget
|
||||
category: Reliability
|
||||
target: Controller
|
||||
controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
minProperties: 1
|
||||
additionalSchemaStrings:
|
||||
policy/PodDisruptionBudget: |
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: ["selector"]
|
||||
properties:
|
||||
selector:
|
||||
type: object
|
||||
required: ["matchLabels"]
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
anyOf:
|
||||
{{ range $key, $value := .metadata.labels }}
|
||||
- properties:
|
||||
"{{ $key }}":
|
||||
type: string
|
||||
const: {{ $value }}
|
||||
required: ["{{ $key }}"]
|
||||
{{ end }}
|
||||
@@ -8,19 +8,14 @@ controllers:
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- Object
|
||||
required:
|
||||
- spec
|
||||
properties:
|
||||
Object:
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
- replicas
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- replicas
|
||||
properties:
|
||||
replicas:
|
||||
type: integer
|
||||
minimum: 2
|
||||
replicas:
|
||||
type: integer
|
||||
minimum: 2
|
||||
|
||||
@@ -2,15 +2,42 @@ successMessage: Filesystem is read only
|
||||
failureMessage: Filesystem should be read only
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
required:
|
||||
- readOnlyRootFilesystem
|
||||
definitions:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- readOnlyRootFilesystem
|
||||
properties:
|
||||
readOnlyRootFilesystem:
|
||||
const: true
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
properties:
|
||||
readOnlyRootFilesystem:
|
||||
const: true
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
@@ -0,0 +1,18 @@
|
||||
successMessage: disruptionsAllowed is greater than zero
|
||||
failureMessage: disruptionsAllowed is not greater than zero
|
||||
category: Reliability
|
||||
target: policy/PodDisruptionBudget
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
type: object
|
||||
required:
|
||||
- disruptionsAllowed
|
||||
properties:
|
||||
disruptionsAllowed:
|
||||
type: integer
|
||||
minimum: 1
|
||||
@@ -2,12 +2,42 @@ successMessage: Privilege escalation not allowed
|
||||
failureMessage: Privilege escalation should not be allowed
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
definitions:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- allowPrivilegeEscalation
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
const: false
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
not:
|
||||
const: true
|
||||
const: false
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Image pull policy is "Always"
|
||||
failureMessage: Image pull policy should be "Always"
|
||||
category: Images
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Readiness probe is configured
|
||||
failureMessage: Readiness probe should be configured
|
||||
category: Health Checks
|
||||
category: Reliability
|
||||
controllers:
|
||||
exclude:
|
||||
- Job
|
||||
|
||||
@@ -2,12 +2,23 @@ successMessage: Not running as privileged
|
||||
failureMessage: Should not be running as privileged
|
||||
category: Security
|
||||
target: Container
|
||||
schemaTarget: Pod
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
definitions:
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
properties:
|
||||
privileged:
|
||||
not:
|
||||
const: true
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
@@ -1,6 +1,6 @@
|
||||
successMessage: Image tag is specified
|
||||
failureMessage: Image tag should be specified
|
||||
category: Images
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
successMessage: Ingress has TLS configured
|
||||
failureMessage: Ingress does not have TLS configured
|
||||
category: Security
|
||||
target: networking.k8s.io/Ingress
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- tls
|
||||
properties:
|
||||
tls:
|
||||
type: array
|
||||
not:
|
||||
const: null
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -32,22 +32,30 @@ import (
|
||||
)
|
||||
|
||||
var setExitCode bool
|
||||
var onlyShowFailedTests bool
|
||||
var minScore int
|
||||
var auditOutputURL string
|
||||
var auditOutputFile string
|
||||
var auditOutputFormat string
|
||||
var resourceToAudit string
|
||||
var useColor bool
|
||||
var helmChart string
|
||||
var helmValues string
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(auditCmd)
|
||||
auditCmd.PersistentFlags().StringVar(&auditPath, "audit-path", "", "If specified, audits one or more YAML files instead of a cluster.")
|
||||
auditCmd.PersistentFlags().BoolVar(&setExitCode, "set-exit-code-on-danger", false, "Set an exit code of 3 when the audit contains danger-level issues.")
|
||||
auditCmd.PersistentFlags().BoolVar(&onlyShowFailedTests, "only-show-failed-tests", false, "If specified, audit output will only show failed tests.")
|
||||
auditCmd.PersistentFlags().IntVar(&minScore, "set-exit-code-below-score", 0, "Set an exit code of 4 when the score is below this threshold (1-100).")
|
||||
auditCmd.PersistentFlags().StringVar(&auditOutputURL, "output-url", "", "Destination URL to send audit results.")
|
||||
auditCmd.PersistentFlags().StringVar(&auditOutputFile, "output-file", "", "Destination file for audit results.")
|
||||
auditCmd.PersistentFlags().StringVarP(&auditOutputFormat, "format", "f", "json", "Output format for results - json, yaml, or score.")
|
||||
auditCmd.PersistentFlags().StringVarP(&auditOutputFormat, "format", "f", "json", "Output format for results - json, yaml, pretty, or score.")
|
||||
auditCmd.PersistentFlags().BoolVar(&useColor, "color", true, "Whether to use color in pretty format.")
|
||||
auditCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
|
||||
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
|
||||
auditCmd.PersistentFlags().StringVar(&helmChart, "helm-chart", "", "Will fill out Helm template")
|
||||
auditCmd.PersistentFlags().StringVar(&helmValues, "helm-values", "", "Optional flag to add helm values")
|
||||
}
|
||||
|
||||
var auditCmd = &cobra.Command{
|
||||
@@ -58,8 +66,28 @@ var auditCmd = &cobra.Command{
|
||||
if displayName != "" {
|
||||
config.DisplayName = displayName
|
||||
}
|
||||
if helmChart != "" {
|
||||
var err error
|
||||
auditPath, err = ProcessHelmTemplates(helmChart, helmValues)
|
||||
if err != nil {
|
||||
logrus.Infof("Couldn't process helm chart: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat)
|
||||
k, err := kube.CreateResourceProvider(context.TODO(), auditPath, resourceToAudit, config)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
auditData, err := validator.RunAudit(config, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -73,28 +101,55 @@ var auditCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string) validator.AuditData {
|
||||
// Create a kubernetes client resource provider
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, workload)
|
||||
// ProcessHelmTemplates turns helm into yaml to be processed by Polaris or the other tools.
|
||||
func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
|
||||
cmd := exec.Command("helm", "dependency", "update", helmChart)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
auditData, err := validator.RunAudit(ctx, c, k)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params := []string{
|
||||
"template", helmChart,
|
||||
helmChart,
|
||||
"--output-dir",
|
||||
dir,
|
||||
}
|
||||
if helmValues != "" {
|
||||
params = append(params, "--values", helmValues)
|
||||
}
|
||||
|
||||
cmd = exec.Command("helm", params...)
|
||||
output, err = cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFormat string, useColor bool, onlyShowFailedTests bool) {
|
||||
if onlyShowFailedTests {
|
||||
auditData = auditData.RemoveSuccessfulResults()
|
||||
}
|
||||
var outputBytes []byte
|
||||
var err error
|
||||
if outputFormat == "score" {
|
||||
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
|
||||
} else if outputFormat == "yaml" {
|
||||
jsonBytes, err := json.Marshal(auditData)
|
||||
var jsonBytes []byte
|
||||
jsonBytes, err = json.Marshal(auditData)
|
||||
if err == nil {
|
||||
outputBytes, err = yaml.JSONToYAML(jsonBytes)
|
||||
}
|
||||
} else if outputFormat == "pretty" {
|
||||
outputBytes = []byte(auditData.GetPrettyOutput(useColor))
|
||||
} else {
|
||||
outputBytes, err = json.MarshalIndent(auditData, "", " ")
|
||||
}
|
||||
@@ -148,5 +203,4 @@ func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, wor
|
||||
}
|
||||
}
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
@@ -27,10 +27,12 @@ import (
|
||||
var serverPort int
|
||||
var basePath string
|
||||
var loadAuditFile string
|
||||
var listeningAddress string
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(dashboardCmd)
|
||||
dashboardCmd.PersistentFlags().IntVarP(&serverPort, "port", "p", 8080, "Port for the dashboard webserver.")
|
||||
dashboardCmd.PersistentFlags().StringVar(&listeningAddress, "listening-address", "", "Listening Address for the dashboard webserver.")
|
||||
dashboardCmd.PersistentFlags().StringVar(&basePath, "base-path", "/", "Path on which the dashboard is served.")
|
||||
dashboardCmd.PersistentFlags().StringVar(&loadAuditFile, "load-audit-file", "", "Runs the dashboard with data saved from a past audit.")
|
||||
dashboardCmd.PersistentFlags().StringVar(&auditPath, "audit-path", "", "If specified, audits one or more YAML files instead of a cluster.")
|
||||
@@ -59,6 +61,6 @@ var dashboardCmd = &cobra.Command{
|
||||
http.Handle("/", router)
|
||||
|
||||
logrus.Infof("Starting Polaris dashboard server on port %d", serverPort)
|
||||
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil))
|
||||
logrus.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", listeningAddress, serverPort), nil))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
fwebhook "github.com/fairwindsops/polaris/pkg/webhook"
|
||||
k8sConfig "sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
)
|
||||
|
||||
var webhookPort int
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
coverage:
|
||||
range: 50...80
|
||||
|
Before Width: | Height: | Size: 267 KiB |
@@ -5,7 +5,7 @@ kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
# Source: polaris/templates/serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
@@ -15,7 +15,7 @@ metadata:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -31,9 +31,17 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-view
|
||||
@@ -49,7 +57,7 @@ subjects:
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -109,7 +117,9 @@ spec:
|
||||
- command:
|
||||
- polaris
|
||||
- dashboard
|
||||
image: 'quay.io/fairwinds/polaris:1.2'
|
||||
- --port
|
||||
- "8080"
|
||||
image: 'quay.io/fairwinds/polaris:4.0'
|
||||
imagePullPolicy: 'Always'
|
||||
name: dashboard
|
||||
ports:
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
# Source: polaris/templates/serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
@@ -14,31 +14,8 @@ metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.secret.yaml
|
||||
# The name of this secret is static as it is populated by the webhook pod.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
tls.crt: ''
|
||||
tls.key: ''
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -54,51 +31,17 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'secrets'
|
||||
- 'services'
|
||||
verbs:
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- 'watch'
|
||||
- apiGroups:
|
||||
- 'certificates.k8s.io'
|
||||
resources:
|
||||
- 'certificatesigningrequests'
|
||||
- 'certificatesigningrequests/approval'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'update'
|
||||
- 'create'
|
||||
- 'delete'
|
||||
- apiGroups:
|
||||
- 'certificates.k8s.io'
|
||||
resources:
|
||||
- 'signers'
|
||||
resourceNames:
|
||||
- 'kubernetes.io/legacy-unknown'
|
||||
verbs:
|
||||
- 'approve'
|
||||
- apiGroups:
|
||||
- 'admissionregistration.k8s.io'
|
||||
resources:
|
||||
- 'validatingwebhookconfigurations'
|
||||
verbs:
|
||||
- '*'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-view
|
||||
@@ -114,7 +57,7 @@ subjects:
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris
|
||||
@@ -129,57 +72,6 @@ subjects:
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
# required for current controller-runtime bootstrap method
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'secrets'
|
||||
- 'services'
|
||||
verbs:
|
||||
- '*'
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -225,7 +117,7 @@ spec:
|
||||
command:
|
||||
- polaris
|
||||
- webhook
|
||||
image: 'quay.io/fairwinds/polaris:1.2'
|
||||
image: 'quay.io/fairwinds/polaris:4.0'
|
||||
imagePullPolicy: 'Always'
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
@@ -276,93 +168,66 @@ spec:
|
||||
volumes:
|
||||
- name: secret
|
||||
secret:
|
||||
secretName: polaris-webhook
|
||||
secretName: polaris
|
||||
- name: cr-logs
|
||||
emptyDir: {}
|
||||
---
|
||||
# Source: polaris/templates/webhook.job.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
# Source: polaris/templates/webhook.cert.yaml
|
||||
apiVersion: cert-manager.io/v1alpha2
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: polaris-certificate-updater
|
||||
name: polaris-cert
|
||||
namespace: polaris
|
||||
labels:
|
||||
|
||||
app: polaris
|
||||
component: certificate-updater
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
app: polaris
|
||||
component: certificate-updater
|
||||
spec:
|
||||
containers:
|
||||
- name: webhook-certificate-generator
|
||||
image: 'newrelic/k8s-webhook-cert-manager:1.3.0'
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- ./generate_certificate.sh
|
||||
- --service
|
||||
- polaris-webhook
|
||||
- --namespace
|
||||
- polaris
|
||||
- --secret
|
||||
- polaris-webhook
|
||||
- --webhook
|
||||
- polaris-webhook
|
||||
- --webhook-kind
|
||||
- ValidatingWebhookConfiguration
|
||||
resources:
|
||||
limits:
|
||||
cpu: 150m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp/
|
||||
readOnly: false
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
serviceAccountName: polaris-certificates
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
commonName: polaris-webhook.polaris.svc
|
||||
dnsNames:
|
||||
- polaris-webhook.polaris.svc
|
||||
- polaris-webhook.polaris
|
||||
- polaris-webhook
|
||||
- polaris-webhook.polaris.svc.
|
||||
issuerRef:
|
||||
kind: Issuer
|
||||
name: polaris-selfsigned
|
||||
secretName: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.cert.yaml
|
||||
apiVersion: cert-manager.io/v1alpha2
|
||||
kind: Issuer
|
||||
metadata:
|
||||
name: polaris-selfsigned
|
||||
namespace: polaris
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
# Source: polaris/templates/webhook.configuration.yaml
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: ValidatingWebhookConfiguration
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: polaris/polaris-cert
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: ""
|
||||
service:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
path: /validate
|
||||
port: 443
|
||||
failurePolicy: Ignore
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: polaris.fairwinds.com
|
||||
namespaceSelector:
|
||||
|
||||
matchExpressions:
|
||||
- key: control-plane
|
||||
operator: DoesNotExist
|
||||
objectSelector: {}
|
||||
objectSelector:
|
||||
|
||||
{}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
@@ -402,4 +267,4 @@ webhooks:
|
||||
- replicationcontrollers
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
module.exports = {
|
||||
title: "Fairwinds Polaris Documentation",
|
||||
description: "Documentation for Fairwinds Polaris - audit and enforce Kubernetes best practices for your workloads",
|
||||
themeConfig: {
|
||||
docsRepo: "FairwindsOps/polaris",
|
||||
sidebar: [
|
||||
{
|
||||
title: "Polaris",
|
||||
path: "/",
|
||||
sidebarDepth: 0,
|
||||
collapsable: false,
|
||||
children: [
|
||||
{
|
||||
title: "Changelog",
|
||||
path: "/changelog",
|
||||
},
|
||||
{
|
||||
title: "Code of Conduct",
|
||||
path: "/code-of-conduct",
|
||||
},
|
||||
{
|
||||
title: "Contributing",
|
||||
path: "/contributing",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Ways to Run Polaris",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/dashboard",
|
||||
"/admission-controller",
|
||||
"/infrastructure-as-code",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Usage",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/cli",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Customization",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/customization/configuration",
|
||||
"/customization/checks",
|
||||
"/customization/custom-checks",
|
||||
"/customization/exemptions",
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Checks",
|
||||
collapsable: false,
|
||||
sidebarDepth: 0,
|
||||
children: [
|
||||
"/checks/security",
|
||||
"/checks/efficiency",
|
||||
"/checks/reliability",
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// This file is generated from FairwindsOps/documentation-template
|
||||
// DO NOT EDIT MANUALLY
|
||||
|
||||
const fs = require('fs');
|
||||
const npath = require('path');
|
||||
|
||||
const CONFIG_FILE = npath.join(__dirname, 'config-extras.js');
|
||||
const BASE_DIR = npath.join(__dirname, '..');
|
||||
|
||||
const extras = require(CONFIG_FILE);
|
||||
if (!extras.title || !extras.description || !extras.themeConfig.docsRepo) {
|
||||
throw new Error("Please specify 'title', 'description', and 'themeConfig.docsRepo' in config-extras.js");
|
||||
}
|
||||
|
||||
const docFiles = fs.readdirSync(BASE_DIR)
|
||||
.filter(f => f !== "README.md")
|
||||
.filter(f => f !== ".vuepress")
|
||||
.filter(f => f !== "node_modules")
|
||||
.filter(f => npath.extname(f) === '.md' || npath.extname(f) === '');
|
||||
|
||||
const sidebar = [['/', 'Home']].concat(docFiles.map(f => {
|
||||
const ext = npath.extname(f);
|
||||
if (ext === '') {
|
||||
// this is a directory
|
||||
const title = f;
|
||||
const children = fs.readdirSync(npath.join(BASE_DIR, f)).map(subf => {
|
||||
return '/' + f + '/' + npath.basename(subf);
|
||||
});
|
||||
return {title, children};
|
||||
}
|
||||
const path = npath.basename(f);
|
||||
return path;
|
||||
}));
|
||||
|
||||
const baseConfig = {
|
||||
title: "",
|
||||
description: "",
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/favicon.png' }],
|
||||
['script', { src: '/scripts/modify.js' }],
|
||||
['script', { src: '/scripts/marketing.js' }],
|
||||
],
|
||||
themeConfig: {
|
||||
docsRepo: "",
|
||||
docsDir: 'docs',
|
||||
editLinks: true,
|
||||
editLinkText: "Help us improve this page",
|
||||
logo: '/img/fairwinds-logo.svg',
|
||||
heroText: "",
|
||||
sidebar,
|
||||
nav: [
|
||||
{text: 'View on GitHub', link: 'https://github.com/' + extras.themeConfig.docsRepo},
|
||||
],
|
||||
},
|
||||
plugins: {
|
||||
'vuepress-plugin-clean-urls': {
|
||||
normalSuffix: '/',
|
||||
notFoundPath: '/404.html',
|
||||
},
|
||||
'check-md': {},
|
||||
},
|
||||
}
|
||||
|
||||
let config = JSON.parse(JSON.stringify(baseConfig))
|
||||
if (!fs.existsSync(CONFIG_FILE)) {
|
||||
throw new Error("Please add config-extras.js to specify your project details");
|
||||
}
|
||||
for (let key in extras) {
|
||||
if (!config[key]) config[key] = extras[key];
|
||||
else if (key === 'head') config[key] = config[key].concat(extras[key]);
|
||||
else Object.assign(config[key], extras[key]);
|
||||
}
|
||||
module.exports = config;
|
||||
|
After Width: | Height: | Size: 400 B |
@@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 3000 706.2" style="enable-background:new 0 0 3000 706.2;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#231039;}
|
||||
.st1{fill:#6D6D6D;}
|
||||
.st2{font-family:'CircularStd-Book';}
|
||||
.st3{font-size:88.0749px;}
|
||||
.st4{fill:#F36D21;}
|
||||
.st5{fill:none;stroke:#F36D21;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st6{fill:#898989;}
|
||||
.st7{fill:#BCBCBC;}
|
||||
.st8{fill:none;}
|
||||
.st9{fill:#DBDBDB;}
|
||||
.st10{enable-background:new ;}
|
||||
.st11{fill:#23183A;}
|
||||
.st12{fill:#B5B5B5;}
|
||||
.st13{fill:#9F234F;}
|
||||
.st14{fill:#F08117;}
|
||||
.st15{fill:#8ED0D9;}
|
||||
.st16{fill:#A3A3A3;}
|
||||
.st17{fill:#D1D1D1;}
|
||||
.st18{fill:#D1D1D1;stroke:#23183A;stroke-width:2.3711;stroke-miterlimit:10;}
|
||||
.st19{fill:none;stroke:#D1D1D1;stroke-width:6.6619;stroke-miterlimit:10;}
|
||||
.st20{fill:#23103A;}
|
||||
.st21{fill:#270D3C;}
|
||||
.st22{fill:#26163C;}
|
||||
.st23{fill:#F26722;}
|
||||
.st24{fill:#F78F1E;}
|
||||
.st25{fill:#F9AC78;}
|
||||
.st26{fill:#FF6700;}
|
||||
.st27{fill:url(#SVGID_1_);}
|
||||
.st28{fill:url(#SVGID_2_);}
|
||||
.st29{fill:url(#SVGID_3_);}
|
||||
.st30{fill:url(#SVGID_4_);}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M317.7,177.6h-149v-49.3h174.4v23.9C343.1,166.3,331.7,177.6,317.7,177.6z"/>
|
||||
<path class="st0" d="M254.3,247.2h-85.6v-49.3h111v23.9C279.7,235.9,268.3,247.2,254.3,247.2z"/>
|
||||
<path class="st0" d="M192.7,316.8h-24v-49.3H218v23.9C218,305.5,206.7,316.8,192.7,316.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M398.5,287.3V136.7h93.7v24H424V203h61.2v23.6H424v60.8H398.5z"/>
|
||||
<path class="st0" d="M532.1,228l26.6-4c6-0.8,7.6-3.8,7.6-7.4c0-8.7-5.9-15.7-19.5-15.7c-13,0-20.2,8.3-21.2,18.7l-22.5-5.1
|
||||
c1.9-17.8,18.1-33.8,43.6-33.8c31.9,0,44,18.1,44,38.7v51.4c0,9.3,1.1,15.5,1.3,16.6h-22.9c-0.2-0.6-1.1-4.9-1.1-13.2
|
||||
c-4.9,7.9-15.1,16.4-31.9,16.4c-21.7,0-35.1-14.9-35.1-31.2C500.9,240.8,514.5,230.6,532.1,228z M566.3,244.4v-4.7l-27,4
|
||||
c-7.6,1.3-13.8,5.5-13.8,14c0,7,5.3,13.4,15.1,13.4C554.4,271.2,566.3,264.6,566.3,244.4z"/>
|
||||
<path class="st0" d="M632.4,131.4c8.9,0,16.1,7.2,16.1,16.1s-7.2,15.9-16.1,15.9c-8.7,0-15.9-7-15.9-15.9S623.7,131.4,632.4,131.4
|
||||
z M620.3,287.3V183.9h24.4v103.5H620.3z"/>
|
||||
<path class="st0" d="M737.2,208.3c-2.8-0.4-5.5-0.6-8.1-0.6c-19.1,0-27.8,11-27.8,30.4v49.3h-24.6V183.9h24v16.6
|
||||
c4.9-11.3,16.4-17.8,30-17.8c3,0,5.5,0.4,6.6,0.6V208.3z"/>
|
||||
<path class="st0" d="M844.3,183.9l24.4,71.6l20.6-71.6h25.1l-33.1,103.5h-24.6L830.9,213l-25.1,74.4h-25.3L747,183.9h26.3l21,71.6
|
||||
l24.4-71.6H844.3z"/>
|
||||
<path class="st0" d="M945.2,131.4c8.9,0,16.1,7.2,16.1,16.1s-7.2,15.9-16.1,15.9c-8.7,0-15.9-7-15.9-15.9S936.5,131.4,945.2,131.4
|
||||
z M933.1,287.3V183.9h24.4v103.5H933.1z"/>
|
||||
<path class="st0" d="M1014.1,287.3h-24.6V183.9h24v13.8c6.8-11.9,19.1-16.8,30.6-16.8c25.3,0,37.4,18.3,37.4,41v65.4h-24.6v-61.2
|
||||
c0-12.8-5.7-22.9-21.2-22.9c-14,0-21.5,10.8-21.5,24.4V287.3z"/>
|
||||
<path class="st0" d="M1207,268.4c0,9.3,0.9,17.2,1.1,18.9h-23.6c-0.4-2.3-1.1-9.1-1.1-13.4c-4.9,8.7-15.7,15.9-30.4,15.9
|
||||
c-29.7,0-49.7-23.4-49.7-54.6c0-29.8,20.2-54.2,49.3-54.2c18.1,0,27.2,8.3,30.4,14.9v-62.5h24V268.4z M1155.8,268.2
|
||||
c16.1,0,27.4-13.4,27.4-33.1s-11-32.1-27.2-32.1c-16.1,0-28,12.5-28,32.3C1128,255,1139,268.2,1155.8,268.2z"/>
|
||||
<path class="st0" d="M1251.6,253.3c0.9,9.3,7.9,17.6,21,17.6c10.2,0,15.5-5.5,15.5-11.9c0-5.5-3.8-9.8-12.1-11.5l-15.3-3.4
|
||||
c-18.3-4-27.8-15.9-27.8-30.2c0-18.1,16.6-33.4,38-33.4c28.9,0,38.2,18.7,39.9,28.9l-20.8,5.9c-0.9-5.9-5.3-15.5-19.1-15.5
|
||||
c-8.7,0-14.9,5.5-14.9,11.9c0,5.5,4,9.6,10.4,10.8l15.7,3.2c19.5,4.2,29.7,16.2,29.7,31.4c0,15.7-12.7,33.1-39.1,33.1
|
||||
c-30,0-41.4-19.5-42.7-31.2L1251.6,253.3z"/>
|
||||
<path class="st0" d="M1391.2,287.3V136.7h25.9v150.7H1391.2z"/>
|
||||
<path class="st0" d="M1475.8,287.3h-24.6V183.9h24v13.8c6.8-11.9,19.1-16.8,30.6-16.8c25.3,0,37.4,18.3,37.4,41v65.4h-24.6v-61.2
|
||||
c0-12.8-5.7-22.9-21.2-22.9c-14,0-21.5,10.8-21.5,24.4V287.3z"/>
|
||||
<path class="st0" d="M1585.4,253.3c0.9,9.3,7.9,17.6,21,17.6c10.2,0,15.5-5.5,15.5-11.9c0-5.5-3.8-9.8-12.1-11.5l-15.3-3.4
|
||||
c-18.3-4-27.8-15.9-27.8-30.2c0-18.1,16.6-33.4,38-33.4c28.9,0,38.2,18.7,39.9,28.9l-20.8,5.9c-0.9-5.9-5.3-15.5-19.1-15.5
|
||||
c-8.7,0-14.9,5.5-14.9,11.9c0,5.5,4,9.6,10.4,10.8l15.7,3.2c19.5,4.2,29.7,16.2,29.7,31.4c0,15.7-12.7,33.1-39.1,33.1
|
||||
c-30,0-41.4-19.5-42.7-31.2L1585.4,253.3z"/>
|
||||
<path class="st0" d="M1682.3,131.4c8.9,0,16.1,7.2,16.1,16.1s-7.2,15.9-16.1,15.9c-8.7,0-15.9-7-15.9-15.9
|
||||
S1673.6,131.4,1682.3,131.4z M1670.2,287.3V183.9h24.4v103.5H1670.2z"/>
|
||||
<path class="st0" d="M1741.8,287.1c1.7,12.8,11.7,22.3,25.9,22.3c19.5,0,29.3-10,29.3-31v-10.8c-4.5,8.1-14.9,15.1-30,15.1
|
||||
c-27.6,0-47.8-21.2-47.8-50.4c0-27.6,19.3-50.4,47.8-50.4c16.1,0,26.1,6.6,30.6,15.1v-13.2h23.8v93.7c0,27.6-14.2,53.3-53.1,53.3
|
||||
c-27.4,0-46.5-17-49.3-37.8L1741.8,287.1z M1797.7,232.3c0-17.6-11.3-29.1-26.6-29.1c-15.7,0-27,11.5-27,29.1
|
||||
c0,18.1,10.8,29.3,27,29.3C1786.9,261.6,1797.7,250.1,1797.7,232.3z"/>
|
||||
<path class="st0" d="M1876.3,287.3h-24.6V133.5h24.6v60.3c7-9.1,18.7-13,29.5-13c25.5,0,37.8,18.3,37.8,41v65.4H1919v-61.2
|
||||
c0-12.8-5.7-22.9-21.2-22.9c-13.6,0-21,10.2-21.5,23.4V287.3z"/>
|
||||
<path class="st0" d="M2004.9,183.9h21.5v21.9h-21.5v48c0,9.1,4,13,13.2,13c3.4,0,7.4-0.6,8.5-0.8v20.4c-1.5,0.6-6.2,2.3-15.1,2.3
|
||||
c-19.1,0-31-11.5-31-30.8v-52.1h-19.1v-21.9h5.3c11,0,15.9-7,15.9-16.1v-15.5h22.3V183.9z"/>
|
||||
<path class="st0" d="M2065.7,253.3c0.9,9.3,7.9,17.6,21,17.6c10.2,0,15.5-5.5,15.5-11.9c0-5.5-3.8-9.8-12.1-11.5l-15.3-3.4
|
||||
c-18.3-4-27.8-15.9-27.8-30.2c0-18.1,16.6-33.4,38-33.4c28.9,0,38.2,18.7,39.9,28.9l-20.8,5.9c-0.9-5.9-5.3-15.5-19.1-15.5
|
||||
c-8.7,0-14.9,5.5-14.9,11.9c0,5.5,4,9.6,10.4,10.8l15.7,3.2c19.5,4.2,29.7,16.2,29.7,31.4c0,15.7-12.7,33.1-39.1,33.1
|
||||
c-30,0-41.4-19.5-42.7-31.2L2065.7,253.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 2512.0906 491.5612)" class="st1 st2 st3">more</text>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 2088.605 491.561)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 2439.4316 491.561)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 1567.2388 491.561)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
<g>
|
||||
<text transform="matrix(1 0 0 1 1250.0308 491.5612)" class="st4 st2 st3">+</text>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st5" d="M524.4,621.6c0,0,476.7-73.4,685.5-33.6"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M1453,505.9c-0.8-0.1-1.6,0.3-2,1V503h-1.2v9.2h1.2v-0.9c0.4,0.7,1.2,1.1,2,1c1.8,0,2.8-1.4,2.8-3.2
|
||||
C1455.8,507.2,1454.9,505.9,1453,505.9z M1452.8,511.3c-1,0-1.8-0.8-1.8-2.2c-0.1-1,0.6-2,1.6-2.1c0.1,0,0.1,0,0.2,0
|
||||
c1.1,0,1.8,0.8,1.8,2.1C1454.6,510.4,1453.9,511.3,1452.8,511.3z"/>
|
||||
<polygon class="st1" points="1459.4,510.3 1457.5,506.1 1456.2,506.1 1458.8,511.5 1457.3,514.7 1458.6,514.7 1462.6,506.1
|
||||
1461.2,506.1 "/>
|
||||
<path class="st1" d="M1466.4,512.6h1.1c0.5,0,0.9-0.4,0.9-0.9l0,0v-3.2h1.5c0.7,0,1.2-0.6,1.2-1.3v-0.6h-2.8v-2.2h3
|
||||
c0.6,0,1.1-0.5,1.1-1.1v-0.7h-6.2L1466.4,512.6z"/>
|
||||
<path class="st1" d="M1476.2,505.1c-0.7,0-1.4,0.2-2,0.7c-0.5,0.5-0.8,1.1-0.9,1.8h1.8c0-0.2,0.1-0.5,0.3-0.6
|
||||
c0.2-0.2,0.5-0.3,0.8-0.2c0.3,0,0.6,0.1,0.8,0.2c0.2,0.2,0.3,0.5,0.2,0.8v0.2l-1.9,0.3c-0.3,0-0.6,0.1-0.9,0.2
|
||||
c-0.3,0.1-0.5,0.2-0.7,0.4c-0.2,0.1-0.4,0.3-0.5,0.6c-0.1,0.3-0.2,0.6-0.2,0.9c0,0.6,0.2,1.1,0.6,1.5c0.4,0.4,1,0.6,1.6,0.6
|
||||
c0.9,0.1,1.7-0.4,2.1-1.1v0.2c0.1,0.5,0.4,0.8,0.9,0.8h0.8v-4.7c0.1-0.7-0.2-1.5-0.8-2C1477.7,505.3,1476.9,505.1,1476.2,505.1z
|
||||
M1477.2,509.6c0,0.3-0.1,0.6-0.2,0.8c-0.1,0.2-0.3,0.4-0.5,0.5c-0.2,0.1-0.4,0.2-0.7,0.2c-0.2,0-0.4,0-0.6-0.2
|
||||
c-0.1-0.1-0.2-0.3-0.2-0.5s0-0.4,0.1-0.5c0.2-0.1,0.4-0.2,0.6-0.2l1.5-0.3V509.6L1477.2,509.6z"/>
|
||||
<path class="st1" d="M1481.7,502.1c-0.3,0-0.6,0.1-0.8,0.3s-0.4,0.5-0.4,0.8s0.1,0.6,0.3,0.8c0.2,0.2,0.5,0.3,0.8,0.3
|
||||
s0.6-0.1,0.8-0.3s0.4-0.5,0.4-0.8s-0.1-0.6-0.4-0.8C1482.3,502.2,1482,502.1,1481.7,502.1z"/>
|
||||
<path class="st1" d="M1480.8,512.6h0.9c0.5,0,0.9-0.4,0.9-0.9l0,0v-6.4h-1.9v7.3H1480.8z"/>
|
||||
<path class="st1" d="M1486.4,506.4l-0.2-1.1h-1.6v7.3h1c0.5,0,0.9-0.4,0.9-0.9l0,0v-2.8c0-0.5,0.2-1,0.6-1.3
|
||||
c0.4-0.3,0.9-0.5,1.4-0.5v-1.8c-0.5-0.1-1,0.1-1.4,0.3C1486.8,505.7,1486.6,506,1486.4,506.4z"/>
|
||||
<path class="st1" d="M1497.3,509.8l-0.1,0.8l-0.1-0.8l-1.2-4.3h-1.7l-1.2,4.3l-0.1,0.8l-0.1-0.8l-1.1-4.3h-2l2.2,7.3h1.2
|
||||
c0.4,0,0.8-0.3,0.9-0.7l1.1-3.6l1.1,4.2h1.3c0.4,0,0.8-0.3,0.9-0.7l2-6.6h-2L1497.3,509.8z"/>
|
||||
<path class="st1" d="M1502.4,502.1c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.4,0.5-0.4,0.8s0.1,0.6,0.4,0.8c0.2,0.2,0.5,0.3,0.8,0.3
|
||||
s0.6-0.1,0.8-0.3s0.4-0.5,0.4-0.8s-0.1-0.6-0.4-0.8C1503,502.2,1502.7,502.1,1502.4,502.1z"/>
|
||||
<path class="st1" d="M1501.5,512.6h0.9c0.5,0,0.9-0.4,0.9-0.9l0,0v-6.4h-1.9v7.3H1501.5z"/>
|
||||
<path class="st1" d="M1509.1,505.1c-0.4,0-0.8,0.1-1.2,0.3c-0.3,0.2-0.6,0.5-0.8,0.8l-0.1-1h-1.7v7.4h1c0.5,0,1-0.5,0.9-1v-2.8
|
||||
c0-0.5,0.1-0.9,0.4-1.3c0.2-0.3,0.5-0.5,0.9-0.4c0.3,0,0.6,0.1,0.8,0.3c0.2,0.3,0.3,0.6,0.2,1v4.2h0.9c0.6,0,1-0.4,1-1v-3.7
|
||||
c0-0.7-0.2-1.4-0.6-2C1510.4,505.3,1509.7,505.1,1509.1,505.1z"/>
|
||||
<path class="st1" d="M1518.3,506.1c-0.2-0.3-0.5-0.5-0.9-0.7c-0.4-0.2-0.9-0.2-1.3-0.2c-0.9,0-1.8,0.4-2.4,1.1
|
||||
c-0.7,0.7-1,1.7-1,2.7c0,0.7,0.1,1.3,0.4,1.9c0.3,0.6,0.7,1,1.3,1.4c0.5,0.3,1.1,0.5,1.7,0.5c0.9,0.1,1.8-0.4,2.3-1.1v0.2
|
||||
c0.1,0.4,0.5,0.7,1,0.7h0.8v-10.5h-1.9V506.1z M1518.3,509c0,0.5-0.2,1.1-0.6,1.5c-0.3,0.4-0.8,0.6-1.3,0.6s-1-0.2-1.3-0.6
|
||||
c-0.7-0.9-0.7-2.1,0-2.9c0.3-0.4,0.8-0.6,1.3-0.6c0.3,0,0.6,0.1,0.9,0.2c0.3,0.2,0.5,0.4,0.6,0.7c0.2,0.3,0.3,0.6,0.3,1v0.1
|
||||
H1518.3z"/>
|
||||
<path class="st1" d="M1526.8,509c-0.5-0.4-1.1-0.7-1.8-0.8c-0.3-0.1-0.7-0.2-1-0.3c-0.2-0.1-0.3-0.3-0.2-0.5
|
||||
c0-0.2,0.1-0.4,0.2-0.5c0.1-0.2,0.3-0.2,0.6-0.2c0.4-0.1,0.8,0.2,0.8,0.6c0,0.1,0,0.1,0,0.2h1.8c0-0.6-0.2-1.3-0.8-1.7
|
||||
c-0.5-0.4-1.2-0.6-1.9-0.6c-0.7-0.1-1.5,0.2-2,0.7c-0.5,0.4-0.7,1-0.7,1.7c0,0.5,0.2,1,0.6,1.4c0.5,0.4,1.1,0.6,1.7,0.7
|
||||
c0.4,0.1,0.7,0.2,1,0.3c0.2,0.1,0.3,0.3,0.2,0.6c0,0.2-0.1,0.4-0.2,0.5c-0.2,0.2-0.4,0.2-0.7,0.2s-0.5-0.1-0.8-0.2l-0.1-0.2
|
||||
c-0.2-0.3-0.5-0.5-0.8-0.5h-1.2c0,0.7,0.4,1.4,1.1,1.8c0.6,0.4,1.3,0.6,2,0.6c0.4,0,0.7,0,1.1-0.2c0.3-0.1,0.6-0.2,0.9-0.4
|
||||
c0.3-0.2,0.5-0.4,0.6-0.7c0.1-0.3,0.2-0.7,0.2-1.1C1527.3,509.9,1527.1,509.4,1526.8,509z"/>
|
||||
</g>
|
||||
<path class="st1" d="M1334.1,495.3h-9.5v-48.8h9.3v7c3.6-6.1,9.3-8.4,15-8.4c11.7,0,17.5,8.4,17.5,19.3v31h-9.5v-29.3
|
||||
c0-6.8-2.8-12.3-11.4-12.3c-7.5,0-11.4,6-11.4,13.4V495.3z"/>
|
||||
<path class="st1" d="M1460.5,494.5h-9.6l-19.8-48.8h10.8l13.9,37.6l14-37.6h10.2L1460.5,494.5z"/>
|
||||
<path class="st1" d="M1500.3,466.6l13.2-1.9c2.9-0.4,3.8-1.9,3.8-3.7c0-4.8-3.1-8.7-10.6-8.7c-6.7,0-10.5,4.2-11.1,9.8l-9.1-2.1
|
||||
c1-9.3,9.4-15.8,20-15.8c14.6,0,20.3,8.3,20.3,17.9v24.5c0,4.3,0.4,6.8,0.6,8h-9.3c-0.2-1.2-0.5-3-0.5-6.6c-2.1,3.5-7,8.1-15.7,8.1
|
||||
c-9.9,0-16.3-6.9-16.3-14.6C1485.5,472.7,1491.9,467.9,1500.3,466.6z M1517.3,473.6v-2.2l-14.9,2.2c-4.1,0.6-7.1,2.9-7.1,7.3
|
||||
c0,3.7,3,7.1,7.9,7.1C1511,488.1,1517.3,484.3,1517.3,473.6z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M1977.8,504.3c-0.9,0-1.5,0.4-1.7,0.9v-3.4h-1v7.9h1v-0.8c0.3,0.6,0.9,0.9,1.7,0.9c1.6,0,2.4-1.2,2.4-2.8
|
||||
C1980.2,505.5,1979.4,504.3,1977.8,504.3z M1977.6,508.9c-0.9,0-1.6-0.7-1.6-1.9c0-1.1,0.7-1.8,1.6-1.8c1,0,1.6,0.7,1.6,1.8
|
||||
C1979.2,508.1,1978.6,508.9,1977.6,508.9z"/>
|
||||
<polygon class="st1" points="1983.4,508 1981.7,504.4 1980.5,504.4 1982.8,509.1 1981.5,511.9 1982.6,511.9 1986,504.4
|
||||
1984.9,504.4 "/>
|
||||
<path class="st1" d="M1989.4,510h0.9c0.4,0,0.8-0.3,0.8-0.8v-2.8h1.3c0.6,0,1.1-0.5,1.1-1.1v-0.5h-2.4V503h2.6c0.5,0,1-0.4,1-1
|
||||
v-0.6h-5.3V510z"/>
|
||||
<path class="st1" d="M1997.7,503.6c-0.7,0-1.3,0.2-1.7,0.6c-0.5,0.4-0.7,0.9-0.7,1.6h1.6c0-0.2,0.1-0.4,0.3-0.5s0.4-0.2,0.7-0.2
|
||||
s0.5,0,0.7,0.2c0.1,0.2,0.2,0.3,0.2,0.7v0.2l-1.6,0.3c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.4,0.2-0.6,0.3c-0.2,0.1-0.3,0.3-0.4,0.5
|
||||
c-0.1,0.2-0.1,0.4-0.1,0.7c0,0.6,0.2,1,0.5,1.3c0.4,0.3,0.8,0.5,1.4,0.5c0.8,0,1.4-0.3,1.8-1v0.2c0,0.4,0.4,0.7,0.8,0.7h0.7v-4.1
|
||||
c0-0.7-0.2-1.3-0.7-1.7S1998.5,503.6,1997.7,503.6z M1998.5,507.4c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.3-0.4,0.4
|
||||
c-0.2,0.1-0.3,0.1-0.6,0.1s-0.4,0-0.5-0.1c-0.1-0.1-0.2-0.2-0.2-0.4c0-0.2,0-0.3,0.1-0.4c0.1-0.1,0.3-0.2,0.5-0.2l1.3-0.3V507.4z"
|
||||
/>
|
||||
<path class="st1" d="M2002.6,501c-0.3,0-0.5,0.1-0.7,0.3c-0.2,0.2-0.3,0.4-0.3,0.7s0,0.5,0.3,0.7c0.2,0.2,0.4,0.3,0.7,0.3
|
||||
s0.5-0.1,0.7-0.3c0.2-0.2,0.3-0.4,0.3-0.7s-0.1-0.5-0.3-0.7C2003.1,501.1,2002.8,501,2002.6,501z"/>
|
||||
<path class="st1" d="M2001.8,510h0.8c0.4,0,0.8-0.3,0.8-0.8v-5.5h-1.6V510z"/>
|
||||
<path class="st1" d="M2006.5,504.7l-0.2-1h-1.4v6.2h0.9c0.4,0,0.8-0.3,0.8-0.8v-2.4c0-0.6,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.4,1.2-0.4
|
||||
v-1.6c-0.5,0-0.9,0.1-1.2,0.3C2006.9,504.1,2006.7,504.4,2006.5,504.7z"/>
|
||||
<path class="st1" d="M2015.9,507.5l-0.1,0.7l-0.1-0.7l-1-3.8h-1.4l-1,3.8l-0.1,0.7l-0.1-0.7l-1-3.8h-1.7l1.9,6.2h1
|
||||
c0.3,0,0.7-0.2,0.8-0.6l1-3.1l1.1,3.7h1.1c0.3,0,0.7-0.2,0.8-0.6l1.7-5.6h-1.7L2015.9,507.5z"/>
|
||||
<path class="st1" d="M2020.3,501c-0.3,0-0.5,0.1-0.7,0.3c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.4,0.3,0.7,0.3
|
||||
s0.5-0.1,0.7-0.3s0.3-0.4,0.3-0.7s-0.1-0.5-0.3-0.7C2020.8,501.1,2020.6,501,2020.3,501z"/>
|
||||
<path class="st1" d="M2019.5,510h0.8c0.4,0,0.8-0.3,0.8-0.8v-5.5h-1.6V510z"/>
|
||||
<path class="st1" d="M2026,503.6c-0.4,0-0.7,0.1-1,0.3c-0.3,0.2-0.5,0.4-0.7,0.7l-0.1-0.8h-1.4v6.2h0.8c0.4,0,0.8-0.3,0.8-0.8v-2.4
|
||||
c0-0.5,0.1-0.9,0.3-1.1s0.4-0.4,0.8-0.4c0.3,0,0.5,0.1,0.7,0.3c0.1,0.2,0.2,0.5,0.2,0.8v3.7h0.8c0.4,0,0.8-0.3,0.8-0.8v-3.2
|
||||
c0-0.7-0.2-1.3-0.5-1.7C2027.2,503.8,2026.7,503.6,2026,503.6z"/>
|
||||
<path class="st1" d="M2034,504.4c-0.2-0.2-0.4-0.4-0.7-0.6c-0.3-0.1-0.7-0.2-1.1-0.2c-0.8,0-1.5,0.3-2.1,0.9
|
||||
c-0.6,0.6-0.8,1.4-0.8,2.3c0,0.6,0.1,1.1,0.3,1.7c0.3,0.5,0.6,0.9,1,1.2s1,0.4,1.5,0.4c0.9,0,1.6-0.3,2-1l0,0.2
|
||||
c0.1,0.3,0.4,0.7,0.8,0.7h0.7v-9h-1.6V504.4z M2033.9,506.9c0,0.5-0.2,1-0.5,1.3c-0.3,0.3-0.7,0.5-1.1,0.5c-0.5,0-0.8-0.1-1.1-0.5
|
||||
c-0.3-0.3-0.4-0.7-0.4-1.3s0.1-1,0.4-1.3s0.7-0.5,1.1-0.5c0.3,0,0.5,0,0.7,0.2c0.2,0.1,0.4,0.3,0.6,0.6
|
||||
C2033.9,506.1,2033.9,506.4,2033.9,506.9L2033.9,506.9z"/>
|
||||
<path class="st1" d="M2041.3,506.9c-0.3-0.3-0.8-0.5-1.6-0.7c-0.4-0.1-0.7-0.2-0.9-0.3c-0.1-0.1-0.2-0.2-0.2-0.4
|
||||
c0-0.2,0-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.5-0.2c0.4,0,0.7,0.2,0.7,0.7h1.6c0-0.6-0.2-1.1-0.7-1.4c-0.4-0.3-1-0.5-1.6-0.5
|
||||
c-0.7,0-1.3,0.2-1.7,0.6c-0.4,0.4-0.6,0.9-0.6,1.4c0,0.5,0.1,0.9,0.5,1.2c0.3,0.3,0.8,0.5,1.5,0.6c0.4,0.1,0.7,0.2,0.9,0.3
|
||||
c0.1,0.1,0.2,0.3,0.2,0.5c0,0.2-0.1,0.3-0.2,0.4c-0.1,0.1-0.3,0.2-0.6,0.2s-0.5-0.1-0.7-0.2c0,0-0.1-0.1-0.1-0.1
|
||||
c-0.2-0.3-0.4-0.4-0.7-0.4h-1c0,0.6,0.3,1.1,0.9,1.5c0.4,0.3,1,0.5,1.7,0.5c0.3,0,0.6,0,0.9-0.1c0.3,0,0.5-0.2,0.7-0.3
|
||||
c0.2-0.1,0.4-0.3,0.5-0.6s0.2-0.6,0.2-0.9C2041.8,507.6,2041.6,507.2,2041.3,506.9z"/>
|
||||
<g>
|
||||
<path class="st1" d="M2011.4,483.3c0.7,4.7,5.5,12.1,16.9,12.1c10.3,0,15.2-7,15.2-12.9c0-6.1-4-10.7-11.5-12.3l-6.7-1.5
|
||||
c-3.2-0.7-4.8-2.5-4.8-5c0-2.8,2.8-5.5,6.9-5.5c6.6,0,8.3,4.8,8.6,6.9l7.2-2.7c-0.9-3.7-4.4-11.1-15.8-11.1
|
||||
c-8.1,0-14.8,6-14.8,13.1c0,5.9,3.9,10.3,10.4,11.7l7.1,1.6c3.5,0.8,5.4,2.8,5.4,5.4c0,2.9-2.5,5.5-7.1,5.5
|
||||
c-5.9,0-9.2-3.5-9.7-7.8L2011.4,483.3z M2007.1,452.6h-11.2l-18,18.4v-39.4h-8v62.5h8v-12.5l5.8-5.8l13.6,18.3h10.2l-18-24
|
||||
L2007.1,452.6z M1941.6,458.8c7.1,0,10.1,4.7,11.1,8.8l7.3-3.1c-1.7-6.3-7.4-13.2-18.5-13.2c-11.8,0-20.9,9.1-20.9,22
|
||||
c0,12.6,8.9,22.1,21,22.1c10.8,0,16.8-7,18.7-13l-7.2-3.1c-0.9,3.3-3.8,8.6-11.6,8.6c-6.7,0-12.7-5.1-12.7-14.6
|
||||
C1929,463.6,1935,458.8,1941.6,458.8 M1892.5,451.3c-12.4,0-21.2,9.4-21.2,22c0,12.7,8.8,22.1,21.2,22.1
|
||||
c12.4,0,21.2-9.4,21.2-22.1C1913.8,460.7,1905,451.3,1892.5,451.3 M1892.5,488.1c-6.9,0-13-5.2-13-14.9c0-9.6,6-14.8,13-14.8
|
||||
c6.9,0,13,5.2,13,14.8C1905.5,483,1899.4,488.1,1892.5,488.1 M1860.9,431.6h-8.1v62.5h8.1V431.6z M1829.1,436.8
|
||||
c0,3.2,2.6,5.8,5.8,5.8c3.3,0,5.9-2.6,5.9-5.8c0-3.3-2.6-5.9-5.9-5.9C1831.7,431,1829.1,433.6,1829.1,436.8 M1839,452.6h-8v41.5h8
|
||||
V452.6z M1809.3,488.5c0,2.5,0.3,4.7,0.4,5.6h7.9c-0.1-0.7-0.4-3.7-0.4-7.7v-54.8h-8v26.9c-1.4-3.4-5.4-7-12.7-7
|
||||
c-11.7,0-19.7,10-19.7,21.8c0,12.4,7.5,22,19.7,22c6.5,0,10.9-3.5,12.9-7.4V488.5L1809.3,488.5z M1785,473.2
|
||||
c0-8.5,4.8-14.6,12.3-14.6c7.4,0,11.9,5.9,11.9,14.4c0,8.6-4.6,15-12,15C1789.4,488.1,1785,481.7,1785,473.2 M1766.3,431.6h-8.1
|
||||
v62.5h8.1V431.6z M1696.7,494.1v-30h-29.3v7.6h21.2c-0.3,6.3-5.3,16-18.9,16c-11,0-21.3-8-21.3-24.3c0-16.2,10.7-24.1,21.7-24.1
|
||||
c9.5,0,16.7,5.2,19.4,14l7.7-3.3c-3.5-11.1-13.8-18.5-27.1-18.5c-15.1,0-30.3,11.1-30.3,31.9c0,20.7,14.3,32,29.7,32
|
||||
c10.3,0,16.9-5.1,19.6-9.9l0.8,8.6C1689.8,494.1,1696.7,494.1,1696.7,494.1z"/>
|
||||
<g>
|
||||
<path class="st1" d="M1740.6,509.4c-9.3,0-21.3-12.9-29.5-27.8c-4.9-8.8-8.2-17.9-9.5-25.4c-1.8-11.2,1.6-15.6,4.7-17.4
|
||||
c3.1-1.7,8.7-2.3,17.2,5.2c5.8,5,11.7,12.7,16.6,21.5c9.6,17.3,14.3,37.5,4.7,42.8C1743.6,509,1742.1,509.4,1740.6,509.4z
|
||||
M1710.7,444.6c-0.4,0-0.7,0.1-1,0.2c-1.3,0.7-2.2,4.2-1.3,10.3c1.1,6.8,4.2,15,8.7,23.2c10.3,18.6,21.5,25.6,24.4,24
|
||||
c2.9-1.6,2.9-14.9-7.4-33.4c-4.5-8.1-9.9-15.1-15.1-19.6C1715.4,445.9,1712.4,444.6,1710.7,444.6z"/>
|
||||
</g>
|
||||
<polygon class="st6" points="1725.6,495.2 1728.5,497.4 1730.7,494.5 1734,496 1735.4,492.7 1739,493.4 1739.6,489.9
|
||||
1743.2,489.6 1743,486 1746.4,485 1745.4,481.6 1748.4,479.7 1746.6,476.6 1749.1,474.1 1746.6,471.5 1748.4,468.5 1745.4,466.6
|
||||
1746.4,463.2 1743,462.1 1743.2,458.5 1739.6,458.3 1739,454.8 1735.4,455.4 1734,452.1 1730.7,453.6 1728.5,450.8 1725.6,453
|
||||
1722.8,450.8 1720.6,453.6 1717.3,452.1 1715.9,455.4 1712.3,454.8 1711.7,458.3 1708.1,458.5 1708.3,462.1 1704.9,463.2
|
||||
1705.9,466.6 1702.9,468.5 1704.7,471.5 1702.2,474.1 1704.7,476.6 1702.9,479.7 1705.9,481.6 1704.9,485 1708.3,486.1
|
||||
1708.1,489.6 1711.7,489.9 1712.3,493.4 1715.9,492.7 1717.3,496 1720.6,494.5 1722.8,497.4 "/>
|
||||
<g>
|
||||
<path class="st7" d="M1725.6,491.9c9.8,0,17.8-8,17.8-17.8c0-9.8-8-17.8-17.8-17.8c-9.8,0-17.8,8-17.8,17.8
|
||||
C1707.8,483.9,1715.8,491.9,1725.6,491.9"/>
|
||||
<g>
|
||||
<path class="st1" d="M1740.6,509.4c-9.6,0-21.7-13.7-29.5-27.8c-4.9-8.8-8.2-17.9-9.5-25.4c-1.8-11.2,1.6-15.6,4.7-17.4l3.3,6
|
||||
c-1.3,0.7-2.2,4.2-1.3,10.3c1.1,6.8,4.2,15,8.7,23.2c9.3,16.9,20.4,25.4,24.2,24.1l2.2,6.5
|
||||
C1742.6,509.2,1741.6,509.4,1740.6,509.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M2177.7,512.3v-64.9h8.8v7.2c2.5-4.7,8.1-8.3,15.6-8.3c13.9,0,21.4,10.7,21.4,24.3c0,13.9-8,24.6-21.7,24.6
|
||||
c-7.1,0-12.6-3.4-15.1-7.5v24.7L2177.7,512.3L2177.7,512.3z M2200.4,454.3c-8.3,0-13.9,7-13.9,16.3c0,9.6,5.5,16.4,13.9,16.4
|
||||
c8.4,0,13.8-6.9,13.8-16.4C2214.2,461.3,2208.9,454.3,2200.4,454.3z"/>
|
||||
<path class="st1" d="M2235.1,493.9v-70h9.1v70.1L2235.1,493.9L2235.1,493.9z"/>
|
||||
<path class="st1" d="M2276.1,495.3c-10.8,0-17.4-8.1-17.4-18.7v-29.3h9.1v27.9c0,6.4,2.9,12,10.5,12c7.3,0,11-4.9,11-11.9v-28h9.1
|
||||
v38c0,3.7,0.3,7,0.5,8.6h-8.7c-0.2-1.1-0.4-3.7-0.4-5.6C2287.3,493.1,2281.5,495.3,2276.1,495.3z"/>
|
||||
<path class="st1" d="M2326.2,447.4h10.1v8.2h-10.1v24.2c0,4.3,1.7,6.4,6.5,6.4c1.2,0,2.8-0.2,3.6-0.4v7.7c-0.8,0.3-3.1,0.9-6.2,0.9
|
||||
c-7.9,0-13-4.9-13-13.3v-25.5h-8.9v-8.2h2.5c5.1,0,7.1-3.1,7.1-7.1v-7.6h8.3V447.4z"/>
|
||||
<rect x="2353.9" y="456.8" class="st8" width="26.1" height="26.1"/>
|
||||
<g id="XMLID_3_">
|
||||
<g>
|
||||
<path class="st1" d="M2367.8,445.7c15.1,0,25.8,10.5,25.8,24.7c0,14.2-10.7,24.8-25.8,24.8c-15.1,0-25.8-10.5-25.8-24.8
|
||||
C2342,456.2,2352.7,445.7,2367.8,445.7z M2384.9,470.1c0-9.4-7.7-17.1-17.1-17.1s-17.1,7.7-17.1,17.1c0,9.4,7.7,17.1,17.1,17.1
|
||||
C2377.2,487.2,2384.9,479.6,2384.9,470.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M2320.6,504.5c-1,0-1.7,0.5-1.9,1v-3.8h-1.1v8.8h1.1v-0.9c0.4,0.6,1,1,1.9,1c1.7,0,2.7-1.4,2.7-3.1
|
||||
C2323.3,505.9,2322.4,504.5,2320.6,504.5z M2320.4,509.7c-1,0-1.7-0.8-1.7-2.1c0-1.3,0.7-2.1,1.7-2.1c1.1,0,1.7,0.8,1.7,2.1
|
||||
C2322.1,508.9,2321.4,509.7,2320.4,509.7z"/>
|
||||
<polygon class="st1" points="2326.8,508.7 2324.9,504.7 2323.6,504.7 2326.1,509.9 2324.7,513 2325.9,513 2329.7,504.7
|
||||
2328.5,504.7 "/>
|
||||
<path class="st1" d="M2333.4,510.9h1c0.5,0,0.9-0.4,0.9-0.9V507h1.5c0.7,0,1.2-0.6,1.2-1.2v-0.5h-2.7v-2.1h2.9
|
||||
c0.6,0,1.1-0.5,1.1-1.1v-0.7h-5.9V510.9z"/>
|
||||
<path class="st1" d="M2342.7,503.8c-0.8,0-1.5,0.2-1.9,0.7c-0.5,0.4-0.8,1-0.8,1.7h1.7c0-0.2,0.1-0.4,0.3-0.6s0.4-0.2,0.7-0.2
|
||||
c0.3,0,0.6,0,0.7,0.2c0.1,0.2,0.2,0.4,0.2,0.7v0.2l-1.8,0.3c-0.3,0-0.6,0.1-0.9,0.2c-0.2,0.1-0.5,0.2-0.7,0.4
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.6s-0.1,0.5-0.1,0.8c0,0.6,0.2,1.2,0.6,1.5c0.4,0.4,0.9,0.6,1.6,0.6c0.9,0,1.6-0.3,2-1.1v0.2
|
||||
c0,0.4,0.4,0.8,0.9,0.8h0.7v-4.6c0-0.8-0.2-1.5-0.7-1.9C2344.2,504,2343.6,503.8,2342.7,503.8z M2343.7,508c0,0.3-0.1,0.6-0.2,0.8
|
||||
c-0.1,0.2-0.3,0.3-0.5,0.4c-0.2,0.1-0.4,0.1-0.6,0.1c-0.2,0-0.4,0-0.6-0.1c-0.1-0.1-0.2-0.2-0.2-0.4c0-0.2,0-0.4,0.1-0.5
|
||||
c0.1-0.1,0.3-0.2,0.6-0.2l1.4-0.3V508z"/>
|
||||
<path class="st1" d="M2348.1,500.9c-0.3,0-0.5,0.1-0.7,0.3c-0.2,0.2-0.3,0.5-0.3,0.8c0,0.3,0,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.8,0.3
|
||||
c0.3,0,0.6-0.1,0.8-0.3c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.6-0.3-0.8C2348.7,501,2348.4,500.9,2348.1,500.9z"/>
|
||||
<path class="st1" d="M2347.2,510.9h0.9c0.5,0,0.9-0.4,0.9-0.9V504h-1.8V510.9z"/>
|
||||
<path class="st1" d="M2352.5,505l-0.2-1.1h-1.6v7h1c0.5,0,0.9-0.4,0.9-0.9v-2.7c0-0.6,0.2-1,0.5-1.3c0.3-0.3,0.8-0.4,1.4-0.4v-1.7
|
||||
c-0.5,0-1,0.1-1.3,0.3C2352.9,504.4,2352.7,504.7,2352.5,505z"/>
|
||||
<path class="st1" d="M2362.9,508.2l-0.1,0.7l-0.1-0.7l-1.2-4.2h-1.6l-1.2,4.2l-0.1,0.7l-0.1-0.7l-1.1-4.2h-1.9l2.1,6.9h1.1
|
||||
c0.4,0,0.7-0.2,0.9-0.6l1.1-3.5l1.3,4.1h1.2c0.4,0,0.8-0.2,0.9-0.6l1.9-6.3h-1.9L2362.9,508.2z"/>
|
||||
<path class="st1" d="M2367.9,500.9c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.6,0.3,0.7
|
||||
c0.2,0.2,0.5,0.3,0.8,0.3c0.3,0,0.5-0.1,0.8-0.3c0.2-0.2,0.3-0.4,0.3-0.7s-0.1-0.5-0.3-0.7C2368.4,501,2368.2,500.9,2367.9,500.9z
|
||||
"/>
|
||||
<path class="st1" d="M2367,510.9h0.9c0.5,0,0.9-0.4,0.9-0.9V504h-1.8V510.9z"/>
|
||||
<path class="st1" d="M2374.2,503.8c-0.5,0-0.8,0.1-1.2,0.3c-0.3,0.2-0.5,0.4-0.7,0.8l-0.1-0.9h-1.6v7h0.9c0.5,0,0.9-0.4,0.9-0.9
|
||||
v-2.7c0-0.6,0.1-1,0.3-1.3s0.5-0.4,0.9-0.4c0.3,0,0.6,0.1,0.7,0.3c0.1,0.2,0.2,0.5,0.2,0.9v4.1h0.9c0.5,0,0.9-0.4,0.9-0.9v-3.6
|
||||
c0-0.8-0.2-1.4-0.6-1.9C2375.5,504,2375,503.8,2374.2,503.8z"/>
|
||||
<path class="st1" d="M2383.1,504.7c-0.2-0.2-0.4-0.4-0.8-0.6c-0.4-0.1-0.8-0.2-1.3-0.2c-0.9,0-1.7,0.3-2.3,1
|
||||
c-0.6,0.7-0.9,1.6-0.9,2.6c0,0.7,0.1,1.3,0.4,1.8c0.3,0.6,0.7,1,1.2,1.3c0.5,0.3,1.1,0.5,1.7,0.5c1,0,1.7-0.3,2.2-1.1l0,0.2
|
||||
c0.1,0.4,0.4,0.7,0.9,0.7h0.8v-10h-1.8V504.7z M2383,507.5c0,0.6-0.2,1.1-0.5,1.4s-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.1-1.3-0.5
|
||||
c-0.3-0.3-0.5-0.8-0.5-1.4s0.1-1.1,0.5-1.4c0.3-0.3,0.8-0.5,1.3-0.5c0.3,0,0.6,0,0.8,0.2c0.2,0.1,0.4,0.3,0.6,0.6
|
||||
C2382.9,506.6,2383,507,2383,507.5L2383,507.5z"/>
|
||||
<path class="st1" d="M2391.2,507.5c-0.3-0.3-0.9-0.6-1.7-0.7c-0.5-0.1-0.8-0.2-1-0.3c-0.1-0.1-0.2-0.2-0.2-0.5
|
||||
c0-0.2,0-0.3,0.2-0.5c0.1-0.1,0.3-0.2,0.5-0.2c0.5,0,0.8,0.2,0.8,0.8h1.7c0-0.7-0.2-1.2-0.7-1.6c-0.4-0.4-1.1-0.6-1.8-0.6
|
||||
c-0.8,0-1.5,0.2-1.9,0.6c-0.4,0.4-0.7,1-0.7,1.6c0,0.6,0.1,1,0.5,1.4c0.3,0.3,0.9,0.5,1.7,0.7c0.4,0.1,0.8,0.2,1,0.3
|
||||
c0.1,0.1,0.2,0.3,0.2,0.5c0,0.2-0.1,0.4-0.2,0.5c-0.1,0.1-0.3,0.2-0.6,0.2c-0.3,0-0.5-0.1-0.7-0.2c0,0-0.1-0.1-0.1-0.1
|
||||
c-0.2-0.3-0.4-0.5-0.8-0.5h-1.1c0,0.7,0.3,1.3,1,1.7c0.4,0.4,1.1,0.6,1.9,0.6c0.4,0,0.7,0,1-0.1c0.3,0,0.6-0.2,0.8-0.4
|
||||
c0.2-0.1,0.4-0.4,0.6-0.7s0.2-0.6,0.2-1C2391.8,508.2,2391.6,507.8,2391.2,507.5z"/>
|
||||
</g>
|
||||
<path class="st9" d="M2379,461.3c-0.1,0.1-0.1,0.1-0.1,0.2c-0.1,0.5-0.4,0.9-0.6,1.4c-0.3,0.8,0.1,2.7-0.7,3.2
|
||||
c-1,0.6-2.1-1.3-3-2.1c-0.9-0.8-2.5-0.4-3.6-0.7c-0.9-0.2-1.8-0.9-2.8-0.6c-0.8,0.2-1.6,1.3-2.4,1.6c0-0.4,0-0.8-0.4-0.5
|
||||
c-0.9,0.7-1.6,1.5-2.5,2.1c-0.1,0.1-0.1-0.1-0.2,0c-0.1,0.2-1.2,0.1-1.2,0.4c0,1-0.1,2.4-1,2.8c-0.1,0-0.4-1.4-0.5-1.5
|
||||
c-0.1,0-0.9,1-1,0.8c-0.7-0.8-1.3-1.2-2.3-1.7c-0.5-0.3-1.8,0.6-2.2,0.2c-0.1-0.1-0.2-0.2-0.3-0.3c-0.3,1.2-0.5,2.5-0.5,3.7
|
||||
c0,7.9,6.4,14.3,14.3,14.3c7.9,0,14.3-6.4,14.3-14.3C2382.2,466.8,2381,463.7,2379,461.3z"/>
|
||||
<path class="st1" d="M2361.5,466.1c0,1-0.1,2.4-1,2.8c-0.1,0-0.4-1.4-0.5-1.5c-0.1,0-0.9,1-1,0.8c-0.7-0.8-1.3-1.2-2.3-1.7
|
||||
c-0.5-0.3-1.8,0.6-2.2,0.2c-0.1-0.1-0.2-0.2-0.3-0.3c1.7-6.1,7.2-10.5,13.8-10.5c4.5,0,8.5,2.1,11.1,5.3c0,0.1-0.1,0.1-0.1,0.2
|
||||
c-0.1,0.5-0.4,0.9-0.6,1.4c-0.3,0.8,0,2.7-0.7,3.2c-1,0.6-2.1-1.3-3-2.1c-0.9-0.8-2.5-0.4-3.6-0.7c-0.9-0.2-1.8-0.9-2.8-0.6
|
||||
c-0.8,0.2-1.6,1.3-2.4,1.6c0-0.4,0-0.8-0.4-0.5c-0.9,0.7-1.7,1.5-2.5,2.1c-0.1,0.1-0.1-0.1-0.2,0
|
||||
C2362.6,466,2361.5,465.9,2361.5,466.1z"/>
|
||||
<path class="st1" d="M2376.5,472.6c-1.1-0.2-2.1,0.1-2.9,0.8c-0.4-0.9-1.3-1.7-2.4-1.9c-1.8-0.4-3.6,0.8-3.9,2.6
|
||||
c-0.7,3.6,3.9,8.1,4.1,8.3c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0,0.2,0,0.4,0c0.2-0.1,6.3-2.4,7-6C2379.5,474.8,2378.4,473,2376.5,472.6z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<g class="st10">
|
||||
<path class="st11" d="M611.3,540V410.3h17.6v14.5c5-9.3,16.1-16.6,31.1-16.6c27.8,0,42.7,21.5,42.7,48.5
|
||||
c0,27.7-16.1,49.1-43.3,49.1c-14.3,0-25.1-6.8-30.2-15.1V540H611.3z M656.8,424.2c-16.6,0-27.7,13.9-27.7,32.5
|
||||
c0,19.1,11,32.9,27.7,32.9c16.8,0,27.5-13.7,27.5-32.9C684.2,438.1,673.8,424.2,656.8,424.2z"/>
|
||||
<path class="st11" d="M813.4,456.7c0,28.4-19.7,49.5-47.6,49.5c-27.8,0-47.6-21.1-47.6-49.5c0-28.2,19.7-49.3,47.6-49.3
|
||||
C793.7,407.4,813.4,428.4,813.4,456.7z M794.8,456.7c0-21.5-13.5-33.1-29-33.1c-15.5,0-29,11.6-29,33.1c0,21.7,13.5,33.3,29,33.3
|
||||
C781.3,489.9,794.8,478.3,794.8,456.7z"/>
|
||||
<path class="st11" d="M836.6,503.3v-140h18.2v140H836.6z"/>
|
||||
<path class="st11" d="M908.2,450.1l25.1-3.7c5.6-0.8,7.2-3.7,7.2-7c0-9.1-6-16.6-20.1-16.6c-12.8,0-19.9,7.9-21.1,18.8l-17.4-4.1
|
||||
c1.9-17.8,18-30.2,38.1-30.2c27.8,0,38.7,15.9,38.7,34V488c0,8.1,0.8,13,1.2,15.3H942c-0.4-2.3-1-5.8-1-12.6
|
||||
c-4.1,6.6-13.3,15.5-30,15.5c-19,0-31.1-13.1-31.1-27.8C879.9,461.7,892.1,452.4,908.2,450.1z M940.5,463.4v-4.3l-28.4,4.3
|
||||
c-7.7,1.2-13.5,5.6-13.5,13.9c0,7,5.8,13.5,15.1,13.5C928.5,490.9,940.5,483.7,940.5,463.4z"/>
|
||||
<path class="st11" d="M1038.5,428.6c-2.7-0.4-5.4-0.6-7.9-0.6c-15.3,0-25.5,8.1-25.5,28.6v46.6h-18.2v-93h17.8v16.2
|
||||
c6.8-14.3,18.4-17.8,27.8-17.8c2.5,0,5,0.4,6,0.6V428.6z"/>
|
||||
<path class="st11" d="M1066.7,361.9c7.3,0,13.2,5.8,13.2,13.1c0,7.2-5.8,13-13.2,13c-7.2,0-13-5.8-13-13
|
||||
C1053.8,367.7,1059.6,361.9,1066.7,361.9z M1057.8,503.3v-93h18v93H1057.8z"/>
|
||||
<path class="st11" d="M1114.5,473.3c1.2,9.7,8.5,17.4,21.7,17.4c10.2,0,15.9-5.8,15.9-12.4c0-5.8-4.3-10.2-12-12l-15.9-3.5
|
||||
c-14.5-3.1-23.2-13-23.2-26.1c0-15.9,14.9-29.4,33.1-29.4c25.5,0,33.5,16.6,35.4,24.9l-16.1,6c-0.8-4.8-4.6-15.5-19.3-15.5
|
||||
c-9.3,0-15.5,6-15.5,12.4c0,5.6,3.5,9.7,10.6,11.2l15.1,3.3c16.8,3.7,25.7,13.9,25.7,27.7c0,13.1-11,28.8-34,28.8
|
||||
c-25.5,0-36.4-16.4-37.9-27.1L1114.5,473.3z"/>
|
||||
</g>
|
||||
<circle class="st11" cx="765.9" cy="456.2" r="49.7"/>
|
||||
<circle class="st12" cx="787.6" cy="478.1" r="2.8"/>
|
||||
<circle class="st12" cx="744.8" cy="478.5" r="2.8"/>
|
||||
<g>
|
||||
<path class="st13" d="M750.9,431.5c0.6,0.8,1.2,1.6,1.7,2.3c3,3.8,5.9,7.7,8.9,11.5c0.3,0.3,0.3,0.8,0.2,1.1
|
||||
c-0.9,1.5-1.8,3-2.7,4.5c-1.3,2-2.5,4.2-3.8,6.2c0,0,0,0.1-0.1,0.2c0.6-0.4,1.2-0.7,1.7-1.1c3-1.8,6-3.7,9-5.5
|
||||
c0.4-0.2,0.8-0.1,1.3,0.2c2.8,2.1,5.6,4.4,8.4,6.5c1.7,1.3,3.2,2.5,4.9,3.8c0.1,0.1,0.2,0.2,0.4,0.2c-3.6-4.6-7.1-9.1-10.7-13.7
|
||||
c-0.3-0.4-0.4-0.8-0.2-1.1c1.1-1.8,2.2-3.6,3.3-5.4c1.1-1.7,2.2-3.6,3.2-5.4c-0.4,0.2-0.7,0.4-1.1,0.7c-2.2,1.4-4.5,2.7-6.7,4.1
|
||||
c-1,0.6-1.9,1.2-3,1.8c-0.4,0.3-0.8,0.2-1.3-0.2c-3.1-2.4-6.1-4.8-9.2-7.2C753.9,433.7,752.5,432.6,750.9,431.5
|
||||
C751,431.5,750.9,431.5,750.9,431.5z"/>
|
||||
<path class="st14" d="M772.7,439.7c-0.4,0.3-0.7,0.5-1.1,0.8c-1.8,1.3-3.6,2.5-5.4,3.8c-0.2,0.1-0.4,0.1-0.6,0
|
||||
c-0.9-0.6-1.7-1.3-2.6-1.8c-1.3-0.9-2.5-1.7-3.7-2.6c0,0,0,0-0.1-0.1c0.3,0.4,0.5,0.7,0.7,1.1c1.3,1.8,2.5,3.6,3.8,5.4
|
||||
c0.2,0.2,0.1,0.4,0,0.7c-1,1.4-1.8,2.6-2.8,4c-0.6,0.8-1.1,1.6-1.7,2.3c0,0.1-0.1,0.1-0.1,0.2c2.1-1.6,4.3-3,6.5-4.6
|
||||
c0.2-0.1,0.4-0.1,0.6,0c1.1,0.8,2.1,1.6,3.2,2.2c1.1,0.8,2.1,1.6,3.2,2.2c-0.2-0.2-0.3-0.4-0.5-0.7c-1-1.4-1.8-2.6-2.8-4
|
||||
c-0.4-0.6-0.9-1.2-1.3-1.7c-0.2-0.2-0.2-0.4,0-0.7c1.1-1.5,2-2.9,3.1-4.4C771.7,441,772.2,440.3,772.7,439.7L772.7,439.7z"/>
|
||||
<path class="st14" d="M765.9,434.5c-0.1,0.6-0.2,1.1-0.3,1.7c-0.5,2.7-1,5.4-1.4,8.2c0,0.2-0.3,0.5-0.5,0.5
|
||||
c-1.4,0.2-2.7,0.5-4,0.7c-1.8,0.3-3.7,0.7-5.6,1c0,0-0.1,0-0.2,0.1c0.6,0.1,1.1,0.2,1.6,0.3c2.7,0.5,5.4,1,8.2,1.5
|
||||
c0.3,0.1,0.5,0.3,0.6,0.6c0.4,2,0.7,4,1.1,6c0.2,1.2,0.4,2.3,0.6,3.5c0,0.1,0,0.2,0.1,0.3c0.6-3.3,1.2-6.5,1.7-9.8
|
||||
c0-0.3,0.3-0.5,0.5-0.5c1.7-0.3,3.2-0.6,4.9-0.9c1.7-0.3,3.2-0.6,4.9-0.9c-0.3-0.1-0.7-0.1-1-0.2c-2-0.4-4-0.7-6-1.1
|
||||
c-0.9-0.2-1.7-0.3-2.7-0.5c-0.4-0.1-0.6-0.2-0.6-0.6c-0.4-2.2-0.8-4.4-1.2-6.6C766.2,436.6,766.1,435.5,765.9,434.5L765.9,434.5z"
|
||||
/>
|
||||
<path class="st15" d="M765.9,439.7c-0.1,0.3-0.1,0.6-0.2,0.9c-0.3,1.6-0.5,3.1-0.8,4.7c0,0.1-0.2,0.3-0.3,0.3
|
||||
c-0.8,0.1-1.6,0.3-2.2,0.4c-1.1,0.2-2.1,0.4-3.2,0.6c0,0,0,0-0.1,0c0.3,0.1,0.6,0.1,0.9,0.2c1.6,0.3,3.1,0.5,4.7,0.8
|
||||
c0.2,0,0.3,0.2,0.3,0.3c0.2,1.2,0.4,2.2,0.6,3.4c0.1,0.7,0.2,1.4,0.3,1.9v0.1c0.3-1.8,0.7-3.7,1-5.5c0-0.2,0.1-0.3,0.3-0.3
|
||||
c0.9-0.2,1.8-0.3,2.7-0.5c0.9-0.2,1.8-0.3,2.7-0.5c-0.2,0-0.4-0.1-0.6-0.1c-1.2-0.2-2.2-0.4-3.4-0.6c-0.5-0.1-1-0.2-1.6-0.3
|
||||
c-0.2,0-0.3-0.1-0.4-0.3c-0.2-1.3-0.4-2.5-0.7-3.7C766.1,440.8,766,440.3,765.9,439.7L765.9,439.7z"/>
|
||||
</g>
|
||||
<path class="st16" d="M748.6,465.4l-13.1,10.8c-0.8,0.7-0.4,1.9,0.7,1.9h26.2c1.1,0,1.6-1.4,0.7-1.9l-13.1-10.8
|
||||
C749.5,465.1,749,465.1,748.6,465.4z"/>
|
||||
<path class="st16" d="M782.8,465.4l-13.1,10.8c-0.8,0.7-0.4,1.9,0.7,1.9h26.2c1.1,0,1.6-1.4,0.7-1.9l-13.1-10.8
|
||||
C783.7,465.1,783.1,465.1,782.8,465.4z"/>
|
||||
<path class="st17" d="M764.7,462.4l-23.1,18.9c-1.5,1.2-0.6,3.5,1.3,3.5h46.2c1.8,0,2.7-2.3,1.3-3.5l-23.1-18.9
|
||||
C766.4,461.8,765.4,461.8,764.7,462.4z"/>
|
||||
<path class="st18" d="M765,471.8l-17.7,14.5c-1.1,0.9-0.5,2.7,1,2.7h35.4c1.5,0,2-1.7,1-2.7l-17.8-14.5
|
||||
C766.3,471.3,765.5,471.3,765,471.8z"/>
|
||||
<path class="st17" d="M765.2,474.7l-14.8,12c-0.9,0.8-0.4,2.2,0.8,2.2h29.4c1.2,0,1.7-1.5,0.8-2.2l-14.6-12
|
||||
C766.3,474.3,765.7,474.3,765.2,474.7z"/>
|
||||
<circle class="st19" cx="765.9" cy="456.2" r="34.9"/>
|
||||
<g>
|
||||
<path class="st20" d="M1020.6,524.4c-2,0-3.4,1-3.9,2v-7.7h-2.3v18h2.3V535c0.7,1.3,2.1,2.1,3.9,2.1c3.5,0,5.6-2.8,5.6-6.3
|
||||
C1026,527.2,1024.2,524.4,1020.6,524.4z M1020.1,534.9c-2.1,0-3.5-1.7-3.5-4.3c0-2.6,1.5-4.2,3.5-4.2c2.2,0,3.5,1.6,3.5,4.2
|
||||
C1023.7,533.2,1022.3,534.9,1020.1,534.9z"/>
|
||||
<polygon class="st20" points="1033.2,533 1029.5,524.8 1026.8,524.8 1031.9,535.4 1028.9,541.7 1031.5,541.7 1039.2,524.8
|
||||
1036.7,524.8 "/>
|
||||
<path class="st20" d="M1046.8,537.4h2.1c1,0,1.8-0.8,1.8-1.8v-6.3h3c1.4,0,2.5-1.2,2.5-2.5v-1.1h-5.5v-4.4h6c1.2,0,2.2-1,2.2-2.2
|
||||
v-1.4h-12V537.4z"/>
|
||||
<path class="st20" d="M1065.8,522.9c-1.6,0-3,0.5-4,1.4c-1.1,0.9-1.6,2.1-1.7,3.6h3.6c0-0.5,0.2-0.9,0.6-1.2
|
||||
c0.4-0.3,0.9-0.5,1.5-0.5c0.6,0,1.2,0.1,1.5,0.5c0.3,0.4,0.5,0.8,0.5,1.5v0.5l-3.7,0.7c-0.7,0.1-1.3,0.2-1.8,0.4
|
||||
c-0.5,0.2-1,0.5-1.4,0.8c-0.5,0.3-0.8,0.7-1,1.2s-0.3,1-0.3,1.7c0,1.3,0.5,2.4,1.2,3c0.9,0.8,1.9,1.2,3.2,1.2
|
||||
c1.8,0,3.2-0.7,4.1-2.2v0.4c0.1,0.9,0.9,1.6,1.8,1.6h1.5v-9.3c0-1.7-0.5-3-1.5-3.9C1068.8,523.3,1067.5,522.9,1065.8,522.9z
|
||||
M1067.7,531.6c0,0.6-0.2,1.2-0.5,1.7c-0.3,0.4-0.6,0.7-1,0.9c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.2-0.3
|
||||
c-0.3-0.2-0.4-0.5-0.4-0.9c0-0.5,0.1-0.8,0.3-1c0.3-0.2,0.7-0.4,1.2-0.5l2.9-0.6V531.6z"/>
|
||||
<path class="st20" d="M1076.8,517c-0.6,0-1.1,0.2-1.5,0.6c-0.5,0.4-0.7,1-0.7,1.6c0,0.6,0.1,1.1,0.6,1.5c0.5,0.4,1,0.6,1.6,0.6
|
||||
c0.6,0,1.2-0.2,1.6-0.6c0.5-0.4,0.7-0.9,0.7-1.5c0-0.6-0.2-1.2-0.7-1.6C1078,517.2,1077.4,517,1076.8,517z"/>
|
||||
<path class="st20" d="M1075,537.4h1.9c1,0,1.8-0.8,1.8-1.8v-12.4h-3.7V537.4z"/>
|
||||
<path class="st20" d="M1085.8,525.4l-0.4-2.2h-3.2v14.2h2c1,0,1.8-0.8,1.8-1.8v-5.5c0-1.3,0.4-2.1,1.1-2.7
|
||||
c0.7-0.6,1.7-0.9,2.8-0.9V523c-1.1,0-2,0.2-2.7,0.7C1086.6,524.1,1086.1,524.7,1085.8,525.4z"/>
|
||||
<path class="st20" d="M1107.1,531.9l-0.3,1.5l-0.3-1.5l-2.4-8.5h-3.3l-2.4,8.5l-0.3,1.5l-0.3-1.5l-2.2-8.5h-3.9l4.3,14.1h2.3
|
||||
c0.8,0,1.5-0.5,1.8-1.3l2.3-7l2.6,8.3h2.5c0.8,0,1.6-0.5,1.8-1.3l3.9-12.8h-3.9L1107.1,531.9z"/>
|
||||
<path class="st20" d="M1117.2,517c-0.6,0-1.2,0.2-1.6,0.6c-0.5,0.4-0.7,0.9-0.7,1.5c0,0.7,0.2,1.2,0.7,1.5c0.5,0.4,1,0.6,1.6,0.6
|
||||
c0.6,0,1.1-0.2,1.6-0.6c0.5-0.4,0.7-0.9,0.7-1.5c0-0.6-0.3-1.1-0.7-1.5C1118.3,517.2,1117.8,517,1117.2,517z"/>
|
||||
<path class="st20" d="M1115.3,537.4h1.9c1,0,1.8-0.8,1.8-1.8v-12.4h-3.7V537.4z"/>
|
||||
<path class="st20" d="M1130.1,522.9c-1,0-1.7,0.2-2.4,0.6c-0.6,0.4-1.1,0.9-1.5,1.6l-0.3-1.9h-3.2v14.2h1.9c1,0,1.9-0.8,1.9-1.9
|
||||
V530c0-1.2,0.2-2,0.7-2.6s1-0.9,1.8-0.9c0.6,0,1.2,0.3,1.5,0.7c0.3,0.4,0.5,1.1,0.5,1.9v8.3h1.8c1,0,1.9-0.8,1.9-1.9v-7.3
|
||||
c0-1.7-0.4-2.9-1.2-3.9C1132.7,523.4,1131.6,522.9,1130.1,522.9z"/>
|
||||
<path class="st20" d="M1148.1,524.8c-0.4-0.5-0.9-0.9-1.7-1.3c-0.8-0.3-1.6-0.5-2.6-0.5c-1.9,0-3.5,0.7-4.8,2.1
|
||||
c-1.3,1.4-1.9,3.2-1.9,5.3c0,1.4,0.3,2.6,0.8,3.8c0.6,1.2,1.4,2,2.4,2.7c1,0.7,2.2,1,3.4,1c2.1,0,3.6-0.7,4.6-2.2l0.1,0.4
|
||||
c0.2,0.8,0.9,1.5,1.8,1.5h1.6v-20.4h-3.7V524.8z M1148,530.4c0,1.2-0.4,2.2-1.1,2.9c-0.7,0.7-1.5,1.1-2.5,1.1
|
||||
c-1.1,0-1.9-0.3-2.6-1.1c-0.7-0.7-1-1.7-1-2.9s0.3-2.2,1-2.9s1.6-1.1,2.6-1.1c0.6,0,1.2,0.1,1.7,0.4c0.5,0.2,0.9,0.7,1.3,1.3
|
||||
c0.4,0.6,0.6,1.3,0.6,2.1V530.4z"/>
|
||||
<path class="st20" d="M1164.8,530.4c-0.7-0.7-1.9-1.2-3.6-1.5c-1-0.2-1.6-0.4-2-0.6c-0.3-0.2-0.5-0.5-0.5-1c0-0.4,0.1-0.7,0.4-1
|
||||
c0.2-0.3,0.6-0.4,1.1-0.4c1,0,1.6,0.5,1.6,1.6h3.6c0-1.4-0.5-2.5-1.5-3.3c-0.9-0.8-2.2-1.2-3.7-1.2c-1.7,0-3,0.4-3.9,1.3
|
||||
c-0.9,0.9-1.4,2-1.4,3.3c0,1.2,0.3,2.1,1.1,2.8c0.7,0.6,1.8,1.1,3.4,1.4c0.9,0.2,1.6,0.4,2,0.7c0.3,0.3,0.5,0.6,0.5,1.1
|
||||
c0,0.4-0.2,0.8-0.5,1c-0.3,0.3-0.7,0.4-1.3,0.4c-0.6,0-1.1-0.2-1.5-0.5c-0.1-0.1-0.2-0.2-0.3-0.3c-0.4-0.6-0.9-1-1.6-1h-2.3
|
||||
c0.1,1.4,0.6,2.6,2.1,3.5c0.9,0.8,2.3,1.2,4,1.2c0.8,0,1.4-0.1,2.1-0.3c0.6-0.1,1.2-0.4,1.7-0.8c0.5-0.3,0.9-0.8,1.2-1.4
|
||||
s0.4-1.3,0.4-2.1C1165.9,532,1165.5,531.1,1164.8,530.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<radialGradient id="SVGID_1_" cx="1402.3696" cy="470.6803" r="16.1424" fx="1402.6445" fy="471.3849" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#FFF7F2"/>
|
||||
<stop offset="1" style="stop-color:#6D6D6D"/>
|
||||
</radialGradient>
|
||||
<circle class="st27" cx="1402.4" cy="470.7" r="16.1"/>
|
||||
<path class="st1" d="M1402.4,442.2c17.3,0,29.6,12.1,29.6,28.4c0,16.3-12.3,28.5-29.6,28.5s-29.6-12.1-29.6-28.5
|
||||
S1385.1,442.2,1402.4,442.2z M1422,470.4c0-10.9-8.8-19.7-19.7-19.7c-10.9,0-19.7,8.8-19.7,19.7c0,10.9,8.8,19.7,19.7,19.7
|
||||
c0,0,0,0,0,0C1413.2,490,1422,481.2,1422,470.4L1422,470.4z"/>
|
||||
<radialGradient id="SVGID_2_" cx="1402.3696" cy="470.6803" r="21.6422" fx="1402.7382" fy="471.625" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#FFF7F2"/>
|
||||
<stop offset="1" style="stop-color:#6D6D6D"/>
|
||||
</radialGradient>
|
||||
<polygon class="st28" points="1406.9,470.7 1406.9,470.6 1406.9,470.7 1425.5,450.6 1402.4,466.2 1402.4,466.2 1379.2,450.6
|
||||
1397.8,470.7 1379.2,490.7 1402.4,475.2 1402.3,475.1 1402.4,475.1 1402.4,475.2 1425.5,490.7 1406.9,470.7 "/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 486.5 92.3" style="enable-background:new 0 0 486.5 92.3;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M72.5,24.3H0.4V0.4h84.4V12C84.8,18.8,79.3,24.3,72.5,24.3z"/>
|
||||
<path class="st0" d="M41.8,58H0.4V34.1h53.7v11.6C54.1,52.5,48.6,58,41.8,58z"/>
|
||||
<path class="st0" d="M12,91.7H0.4V67.8h23.9v11.6C24.2,86.2,18.7,91.7,12,91.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M121.7,77V16.9h36.9v4.2c0,3.7-3,6.8-6.8,6.8h-18.4v13.5H150v3.5c0,4.1-3.3,7.5-7.5,7.5h-9.1v19.1 c0,3-2.5,5.5-5.5,5.5H121.7z"/>
|
||||
<path class="st0" d="M161.1,64.7c0-2,0.3-3.7,1-5.1c0.7-1.5,1.6-2.7,2.9-3.6c1.3-1,2.7-1.7,4.2-2.3c1.6-0.6,3.3-1,5.4-1.3l11.3-2 v-1.5c0-2-0.5-3.5-1.6-4.6c-1-1.1-2.6-1.6-4.5-1.6s-3.4,0.5-4.6,1.4c-1.2,1-1.8,2.2-1.8,3.8h-10.8c0.3-4.5,1.9-8.2,5.1-11 c3.1-2.8,7.2-4.3,12.3-4.3c5.3,0,9.5,1.3,12.6,4s4.7,6.6,4.7,11.8V77h-4.7c-2.8,0-5.1-2.1-5.5-4.8l-0.1-1.1 c-2.7,4.6-6.9,6.8-12.4,6.8c-3.9,0-7.1-1.2-9.6-3.6C162.4,72,161.1,68.8,161.1,64.7z M172.3,64c0,1.2,0.4,2.1,1.3,2.8 c0.9,0.7,2.1,1,3.7,1c1.4,0,2.7-0.3,3.9-0.9c1.2-0.6,2.2-1.5,3-2.7c1-1.4,1.5-3.2,1.5-5.3v-1.2l-8.7,1.8c-1.6,0.3-2.8,0.8-3.6,1.5 C172.7,61.8,172.3,62.7,172.3,64z"/>
|
||||
<path class="st0" d="M207,20.9c0-1.8,0.7-3.6,2-4.9c1.3-1.2,2.8-1.9,4.6-1.9c1.8,0,3.6,0.6,4.9,1.9c1.4,1.3,2.1,2.9,2.1,4.8 s-0.7,3.4-2,4.7c-1.3,1.3-2.9,1.9-4.8,1.9s-3.5-0.6-4.8-1.9C207.7,24.2,207.1,22.7,207,20.9z M208.2,77V33.5h11.2v38 c0,3-2.5,5.5-5.5,5.5H208.2z"/>
|
||||
<path class="st0" d="M230.4,77V33.5h9.6l1.1,6.8c0.8-2.1,2.3-3.9,4.4-5.2c2.1-1.4,4.9-2.1,8.3-2.1v11c-3.5,0-6.4,0.9-8.6,2.6 s-3.4,4.4-3.5,8.1v16.7c0,3-2.5,5.5-5.5,5.5L230.4,77L230.4,77z"/>
|
||||
<path class="st0" d="M259.6,33.5h11.9l6.6,26.1l0.8,4.6l1-4.6l7.4-26.1h9.9l7.4,26.1l0.9,4.6l0.8-4.6l6.7-26.1h11.9L313,72.9 c-0.7,2.4-3,4.1-5.5,4.1H300l-7.9-25.6L285,73c-0.8,2.3-3,3.9-5.4,3.9h-7L259.6,33.5z"/>
|
||||
<path class="st0" d="M330.5,20.7c0-1.8,0.7-3.4,2-4.7c1.3-1.3,2.9-1.9,4.8-1.9c1.8,0,3.4,0.6,4.8,1.9c1.3,1.3,2,2.8,2,4.7 c0,1.9-0.7,3.4-2,4.7c-1.3,1.3-2.9,1.9-4.8,1.9s-3.5-0.6-4.8-1.9C331.2,24.2,330.5,22.6,330.5,20.7z M331.7,77V33.5h11.2v38 c0,3-2.5,5.5-5.5,5.5H331.7z"/>
|
||||
<path class="st0" d="M354.8,77V33.5h9.6l0.9,5.8c1.1-2,2.6-3.6,4.6-4.9c2-1.2,4.4-1.9,7.2-1.9c4.5,0,8,1.4,10.4,4.3 s3.6,6.8,3.6,11.9v22.5c0,3.2-2.6,5.7-5.7,5.7h-5.5V51.4c0-2.5-0.5-4.5-1.6-5.8c-1-1.3-2.6-2-4.6-2c-2.3,0-4.1,0.9-5.5,2.7 c-1.4,1.8-2.2,4.4-2.2,7.9v17.1c0,3.2-2.6,5.7-5.7,5.7L354.8,77L354.8,77z"/>
|
||||
<path class="st0" d="M399.1,55.2c0-6.4,1.9-11.8,5.7-16.1c3.8-4.3,8.7-6.5,14.5-6.5c2.9,0,5.5,0.5,7.8,1.6s4,2.4,5.2,4.1V14.4 h11.2V77h-4.8c-2.7,0-5-2-5.4-4.7l-0.2-1.2c-3.1,4.5-7.7,6.8-13.9,6.8c-3.8,0-7.3-1-10.4-3s-5.5-4.7-7.2-8.2 C399.9,63.3,399.1,59.4,399.1,55.2z M410.5,55.2c0,3.7,1,6.6,3.1,8.9c2.1,2.3,4.7,3.4,7.8,3.4c2.9,0,5.5-1.1,7.7-3.2 c2.2-2.2,3.3-5.1,3.3-8.8V55c0-2.5-0.6-4.6-1.7-6.5s-2.5-3.2-4.1-4.1s-3.4-1.3-5.2-1.3c-3.1,0-5.7,1.1-7.8,3.4 S410.5,51.6,410.5,55.2z"/>
|
||||
<path class="st0" d="M452.2,63.8h6.9c2,0,3.7,1.2,4.8,2.9c0.2,0.4,0.6,0.7,0.9,1c1.1,0.9,2.6,1.4,4.5,1.4c1.7,0,3-0.4,4-1.1 c0.9-0.7,1.4-1.8,1.4-3c0-1.4-0.5-2.5-1.6-3.3s-3.2-1.5-6.2-2.1c-4.8-0.8-8.3-2.3-10.5-4.4c-2.3-2.1-3.4-4.9-3.4-8.5 c0-4,1.4-7.4,4.2-10.1c2.8-2.7,6.8-4.1,11.9-4.1c4.6,0,8.4,1.3,11.3,3.8c2.9,2.5,4.5,5.8,4.7,9.9H474c-0.1-3.3-1.7-5-5-5 c-1.5,0-2.7,0.4-3.5,1.2c-0.8,0.8-1.3,1.8-1.3,3.1c0,1.4,0.5,2.4,1.6,3.1s3.1,1.3,6,1.8c5.1,0.9,8.8,2.4,10.9,4.5 c2.2,2.1,3.2,5,3.2,8.7c0,2.4-0.4,4.5-1.3,6.4c-0.9,1.8-2.1,3.3-3.7,4.4c-1.6,1.1-3.3,2-5.3,2.5c-2,0.6-4.1,0.8-6.5,0.8 c-5.3,0-9.4-1.3-12.3-3.8C453.9,71.6,452.3,68.2,452.2,63.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
var llcookieless = true;
|
||||
var sf14gv = 32793;
|
||||
(function() {
|
||||
var sf14g = document.createElement('script');
|
||||
sf14g.src = 'https://lltrck.com/lt-v2.min.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(sf14g, s);
|
||||
})();
|
||||
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t,s)}(window,document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', '521127644762074');
|
||||
fbq('track', 'PageView');
|
||||
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-TM95WXQ');
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
setTimeout(function() {
|
||||
var link = document.getElementsByClassName('home-link')[0];
|
||||
linkClone = link.cloneNode(true);
|
||||
linkClone.href = "https://fairwinds.com";
|
||||
link.setAttribute('target', '_blank');
|
||||
link.parentNode.replaceChild(linkClone, link);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
.github-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $primaryColor;
|
||||
}
|
||||
.text-danger {
|
||||
color: $dangerColor;
|
||||
}
|
||||
.text-warning {
|
||||
color: $warningColor;
|
||||
}
|
||||
.text-info {
|
||||
color: $infoColor;
|
||||
}
|
||||
.text-success {
|
||||
color: $successColor;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.2rem solid $warningColor;
|
||||
}
|
||||
blockquote p {
|
||||
color: $warningColor;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom),
|
||||
.page-nav,
|
||||
.page-edit,
|
||||
footer {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom) > h2 {
|
||||
padding-top: 7rem;
|
||||
}
|
||||
|
||||
.navbar .site-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar, .navbar .links {
|
||||
background-color: $primaryColor !important;
|
||||
}
|
||||
|
||||
.navbar .links a {
|
||||
color: #fff;
|
||||
}
|
||||
.navbar .links a svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 5px solid #f7f7f7;
|
||||
}
|
||||
|
||||
.no-border img,
|
||||
img.no-border,
|
||||
header img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mini-img {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom) .mini-img img {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is generated from FairwindsOps/documentation-template
|
||||
* DO NOT EDIT MANUALLY
|
||||
*/
|
||||
|
||||
|
||||
$primaryColor = #23103A
|
||||
$dangerColor = #A0204C
|
||||
$warningColor = #FF6C00
|
||||
$infoColor = #8BD2DC
|
||||
$successColor = #28a745
|
||||
|
||||
$accentColor = #FF6C00
|
||||
$textColor = #2c3e50
|
||||
$borderColor = #eaecef
|
||||
$codeBgColor = #282c34
|
||||
$arrowBgColor = #ccc
|
||||
$badgeTipColor = #42b983
|
||||
$badgeWarningColor = darken(#ffe564, 35%)
|
||||
$badgeErrorColor = #DA5961
|
||||
|
||||
// layout
|
||||
$navbarHeight = 3.6rem
|
||||
$sidebarWidth = 20rem
|
||||
$contentWidth = 740px
|
||||
$homePageWidth = 960px
|
||||
|
||||
// responsive breakpoints
|
||||
$MQNarrow = 959px
|
||||
$MQMobile = 719px
|
||||
$MQMobileNarrow = 419px
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extend: '@vuepress/theme-default'
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<ParentLayout>
|
||||
<template #page-bottom>
|
||||
<div class="custom-footer">
|
||||
<div class="left-footer">
|
||||
<a href="https://fairwinds.com" target="_blank">Learn more about Fairwinds</a>
|
||||
<a href="https://fairwinds.com/insights" target="_blank">Try Fairwinds Insights</a>
|
||||
</div>
|
||||
<div class="right-footer">
|
||||
<a href="https://www.fairwinds.com/privacy-policy" target="_blank">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ParentLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ParentLayout from '@parent-theme/layouts/Layout.vue'
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
|
||||
components: {
|
||||
ParentLayout
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div.custom-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid #eaecef;
|
||||
padding: 2rem 2.5rem;
|
||||
}
|
||||
.custom-footer .left-footer {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.custom-footer .left-footer a:first-of-type {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
.custom-footer a {
|
||||
color: #4e6e8e;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<div align="center" class="no-border">
|
||||
<img src="/img/polaris-logo.png" alt="Polaris Logo">
|
||||
<br>
|
||||
<h3>Best Practices for Kubernetes Workload Configuration</h3>
|
||||
<a href="https://github.com/FairwindsOps/polaris">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.1.6&color=239922">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
|
||||
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
|
||||
</a>
|
||||
<a href="https://circleci.com/gh/FairwindsOps/polaris.svg">
|
||||
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
|
||||
</a>
|
||||
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
|
||||
<img src="https://insights.fairwinds.com/v0/gh/FairwindsOps/polaris/badge.svg">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Fairwinds' Polaris keeps your clusters sailing smoothly. It runs a variety of checks to ensure that
|
||||
Kubernetes pods and controllers are configured using best practices, helping you avoid
|
||||
problems in the future.
|
||||
|
||||
Polaris can be run in three different modes:
|
||||
* As a [dashboard](/dashboard), so you can audit what's running inside your cluster.
|
||||
* As an [admission controller](/admission-controller), so you can automatically reject workloads that don't adhere to your organization's policies.
|
||||
* As a [command-line tool](/infrastructure-as-code), so you can test local YAML files, e.g. as part of a CI/CD process.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture.svg" alt="Polaris Architecture" width="550"/>
|
||||
</p>
|
||||
|
||||
**Want to learn more?** Reach out on [the Slack channel](https://fairwindscommunity.slack.com/messages/polaris) ([request invite](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)), send an email to `opensource@fairwinds.com`, or join us for [office hours on Zoom](https://fairwindscommunity.slack.com/messages/office-hours)
|
||||
|
||||
|
||||
|
||||
## Integration with Fairwinds Insights
|
||||
<p align="center">
|
||||
<img src="/img/FW_Insights_Polaris.svg" alt="Fairwinds Insights" width="550"/>
|
||||
</p>
|
||||
|
||||
[Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
is a platform for auditing Kubernetes clusters and enforcing policy. If you'd like to:
|
||||
* manage Polaris across a fleet of clusters
|
||||
* track findings over time
|
||||
* send results to services like Slack and Datadog
|
||||
* add additional checks from tools like
|
||||
[Trivy](https://github.com/aquasecurity/trivy),
|
||||
[Goldilocks](https://github.com/FairwindsOps/goldilocks/), and
|
||||
[OPA](https://www.openpolicyagent.org)
|
||||
|
||||
you can sign up for a [free account here](https://insights.fairwinds.com?source=polaris).
|
||||
|
||||
## Contributing
|
||||
PRs welcome! Check out the [Contributing Guidelines](/contributing) and [Code of Conduct](/code-of-conduct) for more information.
|
||||
|
||||
## Further Information
|
||||
A history of changes to this project can be viewed in the [Changelog](/changelog)
|
||||
|
||||
If you'd like to learn more about Polaris, or if you'd like to speak with
|
||||
a Kubernetes expert, you can contact `info@fairwinds.com` or [visit our website](https://fairwinds.com)
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation: Polaris can be run as an admission controller that acts as a validating webhook."
|
||||
---
|
||||
# Admission Controller
|
||||
> Want to manage the Admission Controller across multiple clusters? Check out
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
|
||||
Polaris can be run as an admission controller that acts as a validating webhook.
|
||||
This accepts the same configuration as the dashboard, and can run the same validations.
|
||||
|
||||
The webhook will reject any workloads that trigger a danger-level check.
|
||||
This is indicative of the greater goal of Polaris, not just to encourage better
|
||||
configuration through dashboard visibility, but to actually enforce it with this webhook.
|
||||
|
||||
Note that Polaris will not alter your workloads, only block workloads that don't conform to the configured policies.
|
||||
|
||||
## Installation
|
||||
A valid TLS certificate is required for the Polaris Validating Webhook. If you have cert-manager installed in your cluster then the install methods below will work.
|
||||
|
||||
If you don't use cert-manager, you'll need to:
|
||||
|
||||
* Supply a CA Bundle with the `webhook.caBundle`
|
||||
* Create a TLS secret in your cluster with a valid certificate that uses that CA
|
||||
* Pass the name of that secret with the webhook.secretName parameter.
|
||||
|
||||
### kubectl
|
||||
```bash
|
||||
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/webhook.yaml
|
||||
```
|
||||
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace \
|
||||
--set webhook.enable=true --set dashboard.enable=false
|
||||
```
|
||||
|
||||
## Workload Types
|
||||
The webhook comes with built-in support for a handful of known controller types,
|
||||
such as Deployments, Jobs, and DaemonSets. To add new controller types,
|
||||
you can set `webhook.rules` in the
|
||||
[Helm chart](https://github.com/FairwindsOps/charts/tree/master/stable/polaris)
|
||||
|
||||
## Warnings
|
||||
Unfortunately we have not found a way to display warnings as part of `kubectl`
|
||||
output unless we are rejecting a workload altogether.
|
||||
|
||||
This means that any checks with a severity of `warning` will still pass webhook validation,
|
||||
and the only evidence of that warning will either be in the Polaris dashboard or the
|
||||
Polaris webhook logs. This will change in a future version of Kubernetes.
|
||||
@@ -1,18 +1,97 @@
|
||||
# x.x.x (next release)
|
||||
---
|
||||
sidebarDepth: 0
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Changelog"
|
||||
|
||||
# 1.2.1
|
||||
---
|
||||
## 4.0.8
|
||||
* Fix support for namespace checks
|
||||
|
||||
## 4.0.7
|
||||
* Fix goreleaser format
|
||||
|
||||
## 4.0.6
|
||||
* Change goreleaser format
|
||||
* Fix `--helm-values` flag
|
||||
|
||||
## 4.0.5
|
||||
* Bugfix for repeated objects on the dashboard
|
||||
|
||||
## 4.0.4
|
||||
* Bugfix for validating webhook and non-pod checks
|
||||
|
||||
## 4.0.3
|
||||
* Fixed bad interaction between `--set-exit-score-below` and `--only-show-failed-tests`
|
||||
* Dependency updates
|
||||
* Support for Helm chart scanning
|
||||
|
||||
## 4.0.2
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.1
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.0
|
||||
* Add support for arbitrary resources, like Ingress or PodDisruptionBudget
|
||||
* Add support check templating (see docs)
|
||||
* Add support for multi-resource checks (see docs)
|
||||
|
||||
### Breaking Changes
|
||||
* In custom checks, `jsonSchema` is now `schemaString`
|
||||
* Check `pdbDisruptionsAllowedGreaterThanZero` is now called `pdbDisruptionsIsZero`
|
||||
|
||||
## 3.2.0
|
||||
* Add `--format=pretty` option for CLI output
|
||||
|
||||
## 3.1.6
|
||||
* Fix nil pointer issue with --only-output-failed-tests
|
||||
|
||||
## 3.1.5
|
||||
* Fix UI display of Ingress checks
|
||||
|
||||
## 3.1.4
|
||||
* Fixes for exemption annotations for the admission controller
|
||||
|
||||
## 3.1.3
|
||||
* Fixes for `privilegeEscalationAllowed` and `insecureCapabilities` checks to take Kubernetes defaults into account
|
||||
|
||||
## 3.1.2
|
||||
* Start checking deployment configuration using Fairwinds Insights
|
||||
|
||||
## 3.1.1
|
||||
* Updated to alpine:3.13
|
||||
|
||||
## 3.1.0
|
||||
* Added support for Ingress objects
|
||||
* Fixes for exemptions, including support for exempting entire namespaces
|
||||
|
||||
## 3.0.0
|
||||
* **Breaking** - fixed inconsistency in how controller-level checks are handled
|
||||
Custom checks with `target: Controller` should remove `Object` from the top-level of the
|
||||
JSON schema (see changes to `./checks/multipleReplicasForDeployment.yaml`)
|
||||
|
||||
## 2.0.1
|
||||
* Fixed Polaris deployment process
|
||||
|
||||
## 2.0.0
|
||||
* Standardize categories of checks into Security, Reliability, and Efficiency
|
||||
* Changes to the dashboard UI
|
||||
* Update controller-runtime
|
||||
|
||||
## 1.2.1
|
||||
* Update date on dashboard footer
|
||||
|
||||
# 1.2.0
|
||||
## 1.2.0
|
||||
* Add ability to audit a single workload
|
||||
* Enable `pullPolicyAlways` by default
|
||||
* Fix for finding parent resources
|
||||
|
||||
# 1.1.1
|
||||
## 1.1.1
|
||||
* Show controller checks on dashboard
|
||||
* Fix for orphaned pods w/ controller checks
|
||||
|
||||
# 1.1.0
|
||||
## 1.1.0
|
||||
* Add namespace filter in UI
|
||||
* Add priorityClass check
|
||||
* Support reading from STDIN
|
||||
@@ -22,92 +101,92 @@
|
||||
* Fixed case where parent resources trigger error
|
||||
* Fixed UI zero-state
|
||||
|
||||
# 1.0.3
|
||||
## 1.0.3
|
||||
* Fixed case where parent resources trigger error
|
||||
* Fixed dashboard link when `--base-path` is set
|
||||
|
||||
# 1.0.2
|
||||
## 1.0.2
|
||||
* Fixed case where custom CRDs are not covered by RBAC
|
||||
|
||||
# 1.0.1
|
||||
## 1.0.1
|
||||
* Added ARM binaries to releases
|
||||
|
||||
# 1.0.0
|
||||
## New Features
|
||||
## 1.0.0
|
||||
### New Features
|
||||
* Added support for custom checks using JSON Schema
|
||||
* Added support for arbitrary controllers, rather than a pre-configured set
|
||||
* removed support for `controllers_to_scan` in config
|
||||
* Added the ability to exempt a particular controller from a particular check.
|
||||
* Docker image now includes the default config
|
||||
|
||||
## Breaking Changes
|
||||
* Breaking changes in both input and output formats. See [Examples](/examples) for examples of the new formats.
|
||||
### Breaking Changes
|
||||
* Breaking changes in both input and output formats. See [Examples](https://github.com/FairwindsOps/polaris/tree/master/examples) for examples of the new formats.
|
||||
* removed config-level configuration for checks like max/min memory settings
|
||||
* changed severity `error` to `danger`
|
||||
* Breaking changes [to the CLI](/docs/usage.md#cli-options)
|
||||
* Breaking changes to the CLI
|
||||
* CLI flag `--set-exit-code-on-error` is now `--set-exit-code-on-danger`
|
||||
* Flags `--version`, `--dashboard`, `--webhook`, and `--audit` are now arguments
|
||||
* Port flags are now just `--port`
|
||||
|
||||
# 0.6.0
|
||||
## 0.6.0
|
||||
* Fixed webhook support in Kubernetes 1.16
|
||||
* this also removes support for 1.8
|
||||
* Added support for exemptions via controller annotations
|
||||
|
||||
# 0.5.2
|
||||
## 0.5.2
|
||||
* Fixed missing success messages for resource requests/limits
|
||||
|
||||
# 0.5.1
|
||||
## 0.5.1
|
||||
* Added a few more exemptions
|
||||
* Started checking exemptions based on controller name prefix
|
||||
* `runAsUser != 0` now passes the `runAsNonRoot` check
|
||||
|
||||
# 0.5.0
|
||||
## 0.5.0
|
||||
* Added `--load-audit-file` flag to run the dashboard from an existing audit
|
||||
* Added an `ID` field to each check in the output
|
||||
* Skip health checks for jobs, cronjobs, initcontainers
|
||||
* Added support for exemptions
|
||||
* Fixed dashboard base path option
|
||||
|
||||
# 0.4.0
|
||||
## 0.4.0
|
||||
* Added additional Pod Controllers to scan PodSpec (`jobs`, `cronjobs`, `daemonsets`, `replicationcontrollers`)
|
||||
|
||||
# 0.3.1
|
||||
## 0.3.1
|
||||
* Changed dashboard branding to refer to new org name Fairwinds
|
||||
|
||||
# 0.3.0
|
||||
## 0.3.0
|
||||
* Added `--set-exit-code-on-error` and `--set-exit-code-below-score` flags to better support CI/CD
|
||||
|
||||
# 0.2.1
|
||||
## 0.2.1
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/146): Fixed logic on RunAsNonRoot check to incorporate settings in podSpec
|
||||
|
||||
# 0.2.0
|
||||
## 0.2.0
|
||||
* Added `--output-format` flag for better CI/CD support
|
||||
* Added `--display-name` flag
|
||||
* Added support for StatefulSets
|
||||
* Show error message if no kubeconfig is set
|
||||
|
||||
# 0.1.5
|
||||
## 0.1.5
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/125): ignore limits/requests for initContainers
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/132): support custom base path
|
||||
|
||||
# 0.1.4
|
||||
## 0.1.4
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/116): details pages getting template errors
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/114): support all auth providers
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/112): Ignore readiness probe for initContainers
|
||||
|
||||
# 0.1.3
|
||||
## 0.1.3
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/109): dashboard not updating when running persistently
|
||||
|
||||
# 0.1.2
|
||||
## 0.1.2
|
||||
* Stored all third-party assets (e.g. Charts.js) to local files to support offline dashboard viewing
|
||||
* Fix: custom configs in `ConfigMap` not respected
|
||||
|
||||
# 0.1.1
|
||||
## 0.1.1
|
||||
* [Fix](https://github.com/FairwindsOps/polaris/issues/93): missing `config.yaml` and dashboard assets in binary releases
|
||||
* Added some tests and better error handling
|
||||
|
||||
# 0.1.0
|
||||
## 0.1.0
|
||||
* Dashboard fully functional
|
||||
* Validating webhook functional, but still considered beta
|
||||
* Checks:
|
||||
@@ -1,22 +0,0 @@
|
||||
# Health Checks
|
||||
|
||||
Polaris supports validating the presence of readiness and liveness probes in pods.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`healthChecks.readinessProbeMissing` | `warning` | Fails when a readiness probe is not configured for a pod.
|
||||
`healthChecks.livenessProbeMissing` | `warning` | Fails when a liveness probe is not configured for a pod.
|
||||
|
||||
## Background
|
||||
|
||||
Readiness and liveness probes can help maintain the health of applications running inside Kubernetes. By default, Kubernetes only knows whether or not a process is running, not if it's healthy. Properly configured readiness and liveness probes will also be able to ensure the health of an application.
|
||||
|
||||
Readiness probes are designed to ensure that an application has reached a "ready" state. In many cases there is a period of time between when a webserver process starts and when it is ready to receive traffic. A readiness probe can ensure the traffic is not sent to a pod until it is actually ready to receive traffic.
|
||||
|
||||
Liveness probes are designed to ensure that an application stays in a healthy state. When a liveness probe fails, the pod will be restarted.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Kubernetes Docs: Configure Liveness and Readiness Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)
|
||||
- [Utilizing Kubernetes Liveness and Readiness Probes to Automatically Recover From Failure](https://medium.com/spire-labs/utilizing-kubernetes-liveness-and-readiness-probes-to-automatically-recover-from-failure-2fe0314f2b2e)
|
||||
- [Kubernetes Liveness and Readiness Probes: How to Avoid Shooting Yourself in the Foot](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/)
|
||||
@@ -1,19 +0,0 @@
|
||||
# Images
|
||||
|
||||
Polaris supports a number of checks related to the image specified by pods.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`images.tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`images.pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
|
||||
## Background
|
||||
|
||||
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
|
||||
|
||||
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [What's Wrong With The Docker :latest Tag?](https://vsupalov.com/docker-latest-tag/)
|
||||
- [Kubernetes’ AlwaysPullImages Admission Control — the Importance, Implementation, and Security Vulnerability in its Absence](https://medium.com/@trstringer/kubernetes-alwayspullimages-admission-control-the-importance-implementation-and-security-d83ff3815840)
|
||||
@@ -1,22 +0,0 @@
|
||||
# Networking
|
||||
|
||||
Polaris supports a number of checks related to pod networking.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`networking.hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
|
||||
`networking.hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
|
||||
|
||||
|
||||
## Background
|
||||
|
||||
Although Kubernetes allows you to deploy a pod with access to the host network namespace, it's rarely a good idea. A pod running with the `hostNetwork` attribute enabled will have access to the loopback device, services listening on localhost, and could be used to snoop on network activity of other pods on the same node. There are certain examples where setting `hostNetwork` to true is required, such as deploying a networking plugin like Flannel.
|
||||
|
||||
Setting the `hostPort` attribute on a container will ensure that it is accessible on that specific port on each node it is deployed to. Unfortunately when this is specified, it limits where a pod can actually be scheduled in a cluster.
|
||||
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Kubernetes Docs: Configuration Best Practices](https://kubernetes.io/docs/concepts/configuration/overview/#services)
|
||||
|
||||
- [Accessing Kubernetes Pods from Outside of the Cluster](http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/)
|
||||
@@ -1,27 +0,0 @@
|
||||
# Security
|
||||
|
||||
Polaris supports a number of checks related to security.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`security.hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
|
||||
`security.hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
|
||||
`security.notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
|
||||
`security.privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
|
||||
`security.runAsRootAllowed` | `danger` | Fails when `securityContext.runAsNonRoot` is not true.
|
||||
`security.runAsPrivileged` | `danger` | Fails when `securityContext.privileged` is true.
|
||||
`security.insecureCapabilities` | `warning` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](/checks/insecureCapabilities.yaml)
|
||||
`security.dangerousCapabilities` | `danger` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](/checks/dangerousCapabilities.yaml)
|
||||
|
||||
## Background
|
||||
|
||||
Securing workloads in Kubernetes is an important part of overall cluster security. The overall goal should be to ensure that containers are running with as minimal privileges as possible. This includes avoiding privilege escalation, not running containers with a root user, and using read only file systems wherever possible.
|
||||
|
||||
Much of this configuration can be found in the `securityContext` attribute for both Kubernetes pods and containers. Where configuration is available at both a pod and container level, Polaris validates both.
|
||||
|
||||
## Further Reading
|
||||
- [Kubernetes Docs: Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
|
||||
- [KubeCon 2018 Keynote: Running with Scissors](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||
- [Kubernetes Security Book](https://kubernetes-security.info/)
|
||||
- [Kubernetes Docs: Set capabilities for a Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container)
|
||||
- [Linux Programmer's Manual: Capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html)
|
||||
@@ -1,6 +1,12 @@
|
||||
# Resources
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ensure that CPU and memory settings are configured, so that Kubernetes can schedule your workload effectively"
|
||||
---
|
||||
# Efficiency
|
||||
|
||||
Polaris supports a number of checks related to CPU and Memory requests and limits.
|
||||
These checks ensure that CPU and memory settings are configured, so that
|
||||
Kubernetes can schedule your workload effectively.
|
||||
|
||||
## Presence Checks
|
||||
|
||||
@@ -8,10 +14,10 @@ To simplify ensure that these values have been set, the following attributes are
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`resources.cpuRequestsMissing` | `danger` | Fails when `resources.requests.cpu` attribute is not configured.
|
||||
`resources.memoryRequestsMissing` | `danger` | Fails when `resources.requests.memory` attribute is not configured.
|
||||
`resources.cpuLimitsMissing` | `danger` | Fails when `resources.limits.cpu` attribute is not configured.
|
||||
`resources.memoryLimitsMissing` | `danger` | Fails when `resources.limits.memory` attribute is not configured.
|
||||
`cpuRequestsMissing` | `warning` | Fails when `resources.requests.cpu` attribute is not configured.
|
||||
`memoryRequestsMissing` | `warning` | Fails when `resources.requests.memory` attribute is not configured.
|
||||
`cpuLimitsMissing` | `warning` | Fails when `resources.limits.cpu` attribute is not configured.
|
||||
`memoryLimitsMissing` | `warning` | Fails when `resources.limits.memory` attribute is not configured.
|
||||
|
||||
## Background
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Make sure your Kubernetes workloads are always available, and are running the correct image."
|
||||
---
|
||||
# Reliability
|
||||
|
||||
These checks help to make sure your workloads are always available,
|
||||
and are running the correct image.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`readinessProbeMissing` | `warning` | Fails when a readiness probe is not configured for a pod.
|
||||
`livenessProbeMissing` | `warning` | Fails when a liveness probe is not configured for a pod.
|
||||
`tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
|
||||
`multipleReplicasForDeployment` | `ignore` | Fails when there is only one replica for a deployment.
|
||||
`missingPodDisruptionBudget` | `ignore`
|
||||
|
||||
## Background
|
||||
|
||||
Readiness and liveness probes can help maintain the health of applications running inside Kubernetes. By default, Kubernetes only knows whether or not a process is running, not if it's healthy. Properly configured readiness and liveness probes will also be able to ensure the health of an application.
|
||||
|
||||
Readiness probes are designed to ensure that an application has reached a "ready" state. In many cases there is a period of time between when a webserver process starts and when it is ready to receive traffic. A readiness probe can ensure the traffic is not sent to a pod until it is actually ready to receive traffic.
|
||||
|
||||
Liveness probes are designed to ensure that an application stays in a healthy state. When a liveness probe fails, the pod will be restarted.
|
||||
|
||||
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
|
||||
|
||||
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [What's Wrong With The Docker :latest Tag?](https://vsupalov.com/docker-latest-tag/)
|
||||
- [Kubernetes’ AlwaysPullImages Admission Control — the Importance, Implementation, and Security Vulnerability in its Absence](https://medium.com/@trstringer/kubernetes-alwayspullimages-admission-control-the-importance-implementation-and-security-d83ff3815840)
|
||||
- [Kubernetes Docs: Configure Liveness and Readiness Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)
|
||||
- [Utilizing Kubernetes Liveness and Readiness Probes to Automatically Recover From Failure](https://medium.com/spire-labs/utilizing-kubernetes-liveness-and-readiness-probes-to-automatically-recover-from-failure-2fe0314f2b2e)
|
||||
- [Kubernetes Liveness and Readiness Probes: How to Avoid Shooting Yourself in the Foot](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/)
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Security Checks Documentation"
|
||||
---
|
||||
# Security
|
||||
|
||||
These checks are related to security concerns. Workloads that fail these
|
||||
checks may make your cluster more vulnerable, often by introducing a path
|
||||
for privilege escalation.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
|
||||
`hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
|
||||
`notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
|
||||
`privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
|
||||
`runAsRootAllowed` | `warning` | Fails when `securityContext.runAsNonRoot` is not true.
|
||||
`runAsPrivileged` | `danger` | Fails when `securityContext.privileged` is true.
|
||||
`insecureCapabilities` | `warning` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/checks/insecureCapabilities.yaml)
|
||||
`dangerousCapabilities` | `danger` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/checks/dangerousCapabilities.yaml)
|
||||
`hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
|
||||
`hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
|
||||
`tlsSettingsMissing` | `warning` | Fails when an Ingress lacks TLS settings.
|
||||
|
||||
## Background
|
||||
|
||||
Securing workloads in Kubernetes is an important part of overall cluster security. The overall goal should be to ensure that containers are running with as minimal privileges as possible. This includes avoiding privilege escalation, not running containers with a root user, not giving excessive access to the host network, and using read only file systems wherever possible.
|
||||
|
||||
A pod running with the `hostNetwork` attribute enabled will have access to the loopback device, services listening on localhost, and could be used to snoop on network activity of other pods on the same node. There are certain examples where setting `hostNetwork` to true is required, such as deploying a networking plugin like Flannel.
|
||||
|
||||
Setting the `hostPort` attribute on a container will ensure that it is accessible on that specific port on each node it is deployed to. Unfortunately when this is specified, it limits where a pod can actually be scheduled in a cluster.
|
||||
|
||||
Much of this configuration can be found in the `securityContext` attribute for both Kubernetes pods and containers. Where configuration is available at both a pod and container level, Polaris validates both.
|
||||
|
||||
## Further Reading
|
||||
- [Kubernetes Docs: Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
|
||||
- [KubeCon 2018 Keynote: Running with Scissors](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||
- [Kubernetes Security Book](https://kubernetes-security.info/)
|
||||
- [Kubernetes Docs: Set capabilities for a Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container)
|
||||
- [Linux Programmer's Manual: Capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html)
|
||||
- [Kubernetes Docs: Configuration Best Practices](https://kubernetes.io/docs/concepts/configuration/overview/#services)
|
||||
- [Accessing Kubernetes Pods from Outside of the Cluster](http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/)
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Learn your CLI options"
|
||||
---
|
||||
#### CLI Options
|
||||
|
||||
```
|
||||
# top-level commands
|
||||
audit
|
||||
Runs a one-time audit.
|
||||
dashboard
|
||||
Runs the webserver for Polaris dashboard.
|
||||
help
|
||||
Prints help, if you give it a command then it will print help for that command. Same as -h
|
||||
version
|
||||
Prints the version of Polaris
|
||||
webhook
|
||||
Runs the webhook webserver
|
||||
|
||||
# global flags
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
--disallow-exemptions Disallow any exemptions from configuration file.
|
||||
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string Logrus log level. (default "info")
|
||||
|
||||
# dashboard flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
--base-path string Path on which the dashboard is served. (default "/")
|
||||
--display-name string An optional identifier for the audit.
|
||||
-h, --help help for dashboard
|
||||
--listening-address string Listening Address for the dashboard webserver.
|
||||
--load-audit-file string Runs the dashboard with data saved from a past audit.
|
||||
-p, --port int Port for the dashboard webserver. (default 8080)
|
||||
|
||||
# audit flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
--color Whether to use color in pretty format. (default true)
|
||||
--display-name string An optional identifier for the audit.
|
||||
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
|
||||
--helm-chart string Will fill out Helm template
|
||||
--helm-values string Optional flag to add helm values
|
||||
-h, --help help for audit
|
||||
--only-show-failed-tests If specified, audit output will only show failed tests.
|
||||
--output-file string Destination file for audit results.
|
||||
--output-url string Destination URL to send audit results.
|
||||
--resource string Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.
|
||||
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
|
||||
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
|
||||
|
||||
# webhook flags
|
||||
--disable-webhook-config-installer disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.
|
||||
-h, --help help for webhook
|
||||
-p, --port int Port for the dashboard webserver. (default 9876)
|
||||
```
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Code of Conduct"
|
||||
---
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
@@ -71,4 +76,4 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Guidelines"
|
||||
---
|
||||
# Contributing
|
||||
|
||||
Issues, whether bugs, tasks, or feature requests are essential for keeping Polaris great. We believe it should be as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can keep on top of things.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). Please review this document before contributing to this project.
|
||||
This project adheres to a [code of conduct](code-of-conduct.md). Please review this document before contributing to this project.
|
||||
|
||||
## Sign the CLA
|
||||
Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/fairwindsops/polaris).
|
||||
@@ -32,12 +37,26 @@ We label issues with the ["good first issue" tag](https://github.com/FairwindsOp
|
||||
|
||||
The following commands are all required to pass as part of Polaris testing:
|
||||
|
||||
```
|
||||
```bash
|
||||
go list ./... | grep -v vendor | xargs golint -set_exit_status
|
||||
go list ./... | grep -v vendor | xargs go vet
|
||||
go test ./pkg/... -v -coverprofile cover.out
|
||||
```
|
||||
|
||||
### Webhook tests
|
||||
```bash
|
||||
kind create cluster --wait=90s --image kindest/node:v1.15.11 --name polaris-test
|
||||
docker build -t quay.io/fairwinds/polaris:debug . # or use your own registry
|
||||
docker push quay.io/fairwinds/polaris:debug
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
kubectl create ns cert-manager
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
|
||||
POLARIS_IMAGE=quay.io/fairwinds/polaris:debug ./test/webhook_test.sh
|
||||
```
|
||||
to avoid the final cleanup for debugging purposes, you can run
|
||||
```bash
|
||||
SKIP_FINAL_CLEANUP=true IMAGE_TAG=debug ./test/webhook_test.sh
|
||||
```
|
||||
## Creating a New Issue
|
||||
|
||||
If you've encountered an issue that is not already reported, please create a [new issue](https://github.com/FairwindsOps/polaris/issues), choose `Bug Report`, `Feature Request` or `Misc.` and follow the instructions in the template.
|
||||
@@ -50,7 +69,7 @@ Each new pull request should:
|
||||
- Reference any related issues
|
||||
- Add tests that show the issues have been solved
|
||||
- Pass existing tests and linting
|
||||
- Contain a clear indication of if they're ready for review or a work in progress
|
||||
- Contain a clear indication of if they're ready for review, or a work in progress
|
||||
- Be up to date and/or rebased on the master branch
|
||||
|
||||
## Creating a new release
|
||||
@@ -83,10 +102,7 @@ The steps are:
|
||||
1. Clone the helm charts repo
|
||||
1. `git clone https://github.com/FairwindsOps/charts`
|
||||
2. `git checkout -b yourname/update-polaris`
|
||||
1. Bump the version number in:
|
||||
1. stable/polaris/README.md
|
||||
2. stable/polaris/Chart.yaml
|
||||
3. stable/polaris/values.yaml
|
||||
1. Bump the version number in `stable/polaris/Chart.yaml`
|
||||
2. Make any necessary changes to the chart to support the new version of Polaris (e.g. new RBAC permissions)
|
||||
3. **Don't merge yet!**
|
||||
2. Create a PR for this repo
|
||||
@@ -104,4 +120,3 @@ The steps are:
|
||||
3. Make sure CircleCI runs successfully for the new tag - this will push images to quay.io and create a release in GitHub
|
||||
1. If CircleCI fails, check with Codeowners ASAP
|
||||
4. Create and merge a PR for your changes to the Helm chart
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation on setting checks by severity "
|
||||
---
|
||||
# Check Settings
|
||||
Each check can be assigned a `severity`. Only checks with a severity of `danger` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `danger` will result in a change being rejected.
|
||||
|
||||
Polaris validation checks fall into several different categories:
|
||||
|
||||
- [Security](/checks/security.md)
|
||||
- [Reliability](/checks/reliability.md)
|
||||
- [Efficiency](/checks/efficiency.md)
|
||||
|
||||
To change the default severity levels, or to turn checks on or off, you can create your own `config.yaml`:
|
||||
```yaml
|
||||
checks:
|
||||
tagNotSpecified: ignore
|
||||
runAsRootAllowed: danger
|
||||
pullPolicyNotAlways: warning
|
||||
```
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Learn about Polaris defaults and how to customize configurations. "
|
||||
---
|
||||
# Configuration
|
||||
|
||||
The default Polaris configuration can be [seen here](https://github.com/FairwindsOps/polaris/blob/master/examples/config.yaml).
|
||||
|
||||
You can customize the configuration to do things like:
|
||||
* Turn checks [on and off](checks.md)
|
||||
* Change the [severity level](checks.md) of checks
|
||||
* Add new [custom checks](custom-checks.md)
|
||||
* Add [exemptions](exemptions.md) for particular workloads or namespaces
|
||||
|
||||
To pass in your custom configuration, follow the instructions for your environment:
|
||||
|
||||
* CLI - set the `--config` argument to point to your `config.yaml`
|
||||
* Helm - set the `config` variable in your values file
|
||||
* kubectl - create a ConfigMap with your `config.yaml`, mount it as a volume, and use the `--config` argument in your Deployment
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation: Create your own checks, you can use JSON Schema"
|
||||
---
|
||||
# Custom Checks
|
||||
|
||||
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/).
|
||||
This is how built-in Polaris checks are defined as well - you can see all the built-in checks
|
||||
in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/checks) for examples.
|
||||
|
||||
If you write a check that could be useful for others, feel free to open a PR to add it in!
|
||||
|
||||
## Basic Example
|
||||
For example, to disallow images from quay.io:
|
||||
|
||||
```yaml
|
||||
checks:
|
||||
imageRegistry: warning
|
||||
|
||||
customChecks:
|
||||
imageRegistry:
|
||||
successMessage: Image comes from allowed registries
|
||||
failureMessage: Image should not be from disallowed registry
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
type: string
|
||||
not:
|
||||
pattern: ^quay.io
|
||||
```
|
||||
|
||||
## Available Options
|
||||
All custom checks should go under the `customChecks` field in your Polaris config, keyed by the
|
||||
check ID. Note that you'll also have to set its severity in the `checks` section of your Polaris config.
|
||||
|
||||
* `successMessage` - the message to show when the check succeeds
|
||||
* `failureMessage` - the message to show when the check fails
|
||||
* `category` - one of `Security`, `Efficiency`, or `Reliability`
|
||||
* `target` - specifies the type of resource to check. This can be:
|
||||
* a group and kind, e.g. `apps/Deployment` or `networking.k8s.io/Ingress`
|
||||
* `Controller`, to check _any_ resource that contains a pod spec (e.g. Deployments, CronJobs, StatefulSets), as well as naked Pods
|
||||
* `Pod`, same as `Controller`, but the schema applies to the Pod spec rather than the top-level controller
|
||||
* `Container` same as `Controller`, but the schema applies to all Container specs rather than the top-level controller
|
||||
* `controllers` - if `target` is `Controller`, `Pod` or `Container`, you can use this to change which types of controllers are checked
|
||||
* `controllers.include` - _only_ check these controllers
|
||||
* `controllers.exclude` - check all controllers except these
|
||||
* `containers` - if `target` is `Container`, you can use this to decide if `initContainers`, `containers`, or both should be checked
|
||||
* `containers.exclude` - can be set to a list including `initContainer` or `container`
|
||||
* `schema` - the JSON Schema to check against, as a YAML object
|
||||
* `schemaString` - this JSON Schema to check against, as a YAML or JSON string. See [Templating](#templating) below
|
||||
* Note: only _one_ of `schema` and `schemaString` can be specified.
|
||||
* `additionalSchemas` - see [Multi-Resource Checks](#multi-resource-checks) below
|
||||
* `additionalSchemaStrings` - see [Multi-Resource Checks](#multi-resource-checks) below
|
||||
* Note: only _one_ of `additionalSchemas` and `additionalSchemaStrings` can be specified.
|
||||
|
||||
## Checking CPU and Memory
|
||||
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
|
||||
strings like `1000m` and `1G`. Here's an example check that memory and CPU falls within a certain range.
|
||||
```yaml
|
||||
customChecks:
|
||||
resourceLimits:
|
||||
containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
successMessage: Resource limits are within the required range
|
||||
failureMessage: Resource limits should be within the required range
|
||||
category: Resources
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
properties:
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
- limits
|
||||
properties:
|
||||
limits:
|
||||
type: object
|
||||
required:
|
||||
- memory
|
||||
- cpu
|
||||
properties:
|
||||
memory:
|
||||
type: string
|
||||
resourceMinimum: 100M
|
||||
resourceMaximum: 6G
|
||||
cpu:
|
||||
type: string
|
||||
resourceMinimum: 100m
|
||||
resourceMaximum: "2"
|
||||
```
|
||||
|
||||
## Resource Presence
|
||||
You can test for the presence of a resource in each Namespace. For example, to
|
||||
ensure an AlertmanagerConfig is in every Namespace:
|
||||
```yaml
|
||||
successMessage: Namespace has monitoring configuration
|
||||
failureMessage: Namespace should have monitoring configuration
|
||||
category: Security
|
||||
target: Namespace
|
||||
schema: {}
|
||||
additionalSchemas:
|
||||
monitoring.coreos.com/AlertmanagerConfig: {}
|
||||
```
|
||||
|
||||
## Templating
|
||||
You can also utilize go templating in your JSON schema in order to match one field against another.
|
||||
E.g. here is the built-in check to ensure that the `name` annotation matches the object's name:
|
||||
```yaml
|
||||
successMessage: Label app.kubernetes.io/name matches metadata.name
|
||||
failureMessage: Label app.kubernetes.io/name must match metadata.name
|
||||
target: Controller
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["labels"]
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
required: ["app.kubernetes.io/name"]
|
||||
properties:
|
||||
app.kubernetes.io/name:
|
||||
const: "{{ .metadata.name }}"
|
||||
```
|
||||
|
||||
You can also use the full [Go template syntax](https://golang.org/pkg/text/template/), though
|
||||
you may need to specify your schema as a string in order to use concepts like `range`. E.g.
|
||||
this check ensures that at least one of the object's labels is present in `matchLabels`:
|
||||
```yaml
|
||||
schemaString: |
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: ["selector"]
|
||||
properties:
|
||||
selector:
|
||||
type: object
|
||||
required: ["matchLabels"]
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
anyOf:
|
||||
{{ range $key, $value := .metadata.labels }}
|
||||
- properties:
|
||||
"{{ $key }}":
|
||||
type: string
|
||||
const: {{ $value }}
|
||||
required: ["{{ $key }}"]
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## Multi-Resource Checks
|
||||
You can write checks that span multiple resources. This is helpful for ensuring e.g.
|
||||
that every Deployment has a PDB or an HPA associated with it.
|
||||
|
||||
Here's the check to ensure that every Deployment has a PDB:
|
||||
```yaml
|
||||
successMessage: A PodDisruptionBudget is attached
|
||||
failureMessage: Should have a PodDisruptionBudget
|
||||
category: Reliability
|
||||
target: Controller
|
||||
controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
minProperties: 1
|
||||
additionalSchemaStrings:
|
||||
policy/PodDisruptionBudget: |
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: ["selector"]
|
||||
properties:
|
||||
selector:
|
||||
type: object
|
||||
required: ["matchLabels"]
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
anyOf:
|
||||
{{ range $key, $value := .metadata.labels }}
|
||||
- properties:
|
||||
"{{ $key }}":
|
||||
type: string
|
||||
const: {{ $value }}
|
||||
required: ["{{ $key }}"]
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## JSON vs YAML
|
||||
Schemas can also be specified as JSON strings instead of YAML, for easier copy/pasting:
|
||||
```yaml
|
||||
customChecks:
|
||||
foo:
|
||||
jsonSchema: |
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Exemptions"
|
||||
---
|
||||
# Exemptions
|
||||
Sometimes a workload really does need to do things that Polaris considers insecure. For instance,
|
||||
many of the `kube-system` workloads need to run as root, or need access to the host network. In these
|
||||
cases, we can add **exemptions** to allow the workload to pass Polaris checks.
|
||||
|
||||
Exemptions can be added in a few different ways:
|
||||
- Namespace: By editing the Polaris config.
|
||||
- Controller: By annotating a controller, or editing the Polaris config.
|
||||
- Container: By editing the Polaris config.
|
||||
|
||||
## Annotations
|
||||
To exempt a controller from all checks via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true
|
||||
```
|
||||
|
||||
To exempt a controller from a particular check via annotations, use an annotation in the form of `polaris.fairwinds.com/<check>-exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/cpuRequestsMissing-exempt=true
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
To add exemptions via the config, you have to specify at least one or more of the following:
|
||||
- A namespace
|
||||
- A list of controller names
|
||||
- A list of container names
|
||||
|
||||
You can also specify a list of particular rules. If no rules are specified then every rule is exempted.
|
||||
|
||||
Controller names and container names are matched as a prefix, so an empty string will match every controller or container respectively.
|
||||
|
||||
For example:
|
||||
```yaml
|
||||
exemptions:
|
||||
# exemption valid for all rules on all containers in all controllers in default namespace
|
||||
- namespace: default
|
||||
# exemption valid for hostNetworkSet rule on all containers in dns-controller controller in kube-system namespace
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- dns-controller
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
# exemption valid for hostNetworkSet rule on all containers in dns-controller controller in all namespaces
|
||||
- controllerNames:
|
||||
- dns-controller
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
# exemption valid for hostNetworkSet rule on coredns container in all controllers in kube-system namespace
|
||||
- namespace: kube-system
|
||||
- containerNames:
|
||||
- coredns
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
```
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ways to run Polaris | The Polaris dashboard can be installed on a cluster using kubectl or Helm"
|
||||
---
|
||||
# Dashboard
|
||||
|
||||
> Want to see Polaris results for all your clusters in a single dashboard? Check out
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
|
||||
The Polaris dashboard can be installed on a cluster using kubectl or Helm. It
|
||||
can also be run locally, connecting to your cluster using the credentials stored in your `KUBECONFIG`.
|
||||
|
||||
The dashboard is a good way to understand what workloads inside your cluster or Infrastructure as Code
|
||||
don't conform to best practices.
|
||||
|
||||
## Installation
|
||||
|
||||
### kubectl
|
||||
```bash
|
||||
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/dashboard.yaml
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
|
||||
### Local Binary
|
||||
You'll need a valid `KUBECONFIG` set up for the dashboard to connect to your cluster.
|
||||
|
||||
Binary releases can be dowloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap reactiveops/tap
|
||||
brew install reactiveops/tap/polaris
|
||||
polaris dashboard --port 8080
|
||||
```
|
||||
|
||||
You can also point the dashboard to the local filesystem, instead of a live cluster:
|
||||
```bash
|
||||
polaris dashboard --port 8080 --audit-path=./deploy/
|
||||
```
|
||||
|
||||
### Local Docker container
|
||||
|
||||
```
|
||||
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro quay.io/fairwinds/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
|
||||
```
|
||||
|
||||
## Using the Dashboard
|
||||
The Polaris dashboard is a way to get a simple visual overview of the current state of your Kubernetes workloads as well as a roadmap for what can be improved. The dashboard provides a cluster wide overview as well as breaking out results by category, namespace, and workload.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
|
||||
</p>
|
||||
|
||||
Our default standards in Polaris are rather high, so don’t be surprised if your score is lower than you might expect. A key goal for Polaris was to set a high standard and aim for great configuration by default. If the defaults we’ve included are too strict, it’s easy to adjust the configuration as part of the deployment configuration to better suit your workloads.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Polaris Exit Codes for Audit Runs
|
||||
<dl>
|
||||
<dt>Exit 0</dt>
|
||||
<dd>Successful exit code</dd>
|
||||
<dt>Exit 1</dt>
|
||||
<dd>Could not run audit, or application had a failure while running.</dd>
|
||||
<dt>Exit 2</dt>
|
||||
<dd>Unused</dd>
|
||||
<dt>Exit 3</dt>
|
||||
<dd>Exiting due to `--set-exit-code-on-danger` being set and at least one danger was found after an audit.</dd>
|
||||
<dt>Edit 4</dd>
|
||||
<dd>Exiting due to `--set-exit-code-below-score` being set and the audit resulted in a score less than the minimum score value.</dd>
|
||||
</dl>
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation | Polaris can be used on the command line to audit local Kubernetes manifests stored in YAML files."
|
||||
---
|
||||
# Infrastructure as Code
|
||||
> Want to see results for all your IaC repos in one place? Check out
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
|
||||
Polaris can be used on the command line to audit local Kubernetes manifests stored in YAML files.
|
||||
This is particularly helpful for running Polaris against your infrastructure-as-code as part of a
|
||||
CI/CD pipeline. Use the available [command line flags](#running-in-a-ci-pipeline)
|
||||
to cause CI/CD to fail if your Polaris score drops below a certain threshold, or if any danger-level issues arise.
|
||||
|
||||
|
||||
## Install the CLI
|
||||
To run Polaris against your YAML manifests, e.g. as part of a Continuous Integration process,
|
||||
you'll need to install the CLI.
|
||||
|
||||
Binary releases can be downloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap FairwindsOps/tap
|
||||
brew install FairwindsOps/tap/polaris
|
||||
polaris version
|
||||
```
|
||||
|
||||
## Running in a CI pipeline
|
||||
|
||||
### Set minimum score for an exit code
|
||||
You can tell the CLI to set an exit code if it detects certain issues with your YAML files.
|
||||
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--set-exit-code-on-danger \
|
||||
--set-exit-code-below-score 90
|
||||
```
|
||||
|
||||
### Pretty-print results
|
||||
By default, results are output as JSON. You can get human-readable output with
|
||||
the `--format=pretty` flag:
|
||||
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--format=pretty
|
||||
```
|
||||
|
||||
You can also disable colors and emoji:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--format=pretty \
|
||||
--color=false
|
||||
```
|
||||
|
||||
### Output only showing failed tests
|
||||
The CLI to gives you ability to display results containing only failed tests.
|
||||
For example:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--only-show-failed-tests true
|
||||
```
|
||||
|
||||
### Audit Helm Charts
|
||||
You can audit helm charts using the `--helm-chart` and `--helm-values` flags:
|
||||
```
|
||||
polaris audit \
|
||||
--helm-chart ./deploy/chart \
|
||||
--helm-values ./deploy/chart/values.yml
|
||||
```
|
||||
|
||||
### As Github Action
|
||||
#### Setup polaris action
|
||||
|
||||
This action downloads a version of [polaris](https://github.com/FairwindsOps/polaris) and adds it to the path. It makes the [polaris cli](https://polaris.docs.fairwinds.com/infrastructure-as-code) ready to use in following steps of the same job.
|
||||
|
||||
##### Inputs
|
||||
|
||||
###### `version`
|
||||
|
||||
The release version to fetch. This has to be in the form `<tag_name>`.
|
||||
|
||||
##### Outputs
|
||||
|
||||
###### `version`
|
||||
|
||||
The version number of the release tag.
|
||||
|
||||
##### Example usage
|
||||
|
||||
```yaml
|
||||
uses: fairwindsops/polaris@master
|
||||
with:
|
||||
version: "3.0.3"
|
||||
```
|
||||
|
||||
Example inside a job:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup polaris
|
||||
uses: fairwindsops/polaris@master
|
||||
with:
|
||||
version: 3.0.3
|
||||
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation"
|
||||
---
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"author": "",
|
||||
"bugs": {
|
||||
"url": "https://github.com/FairwindsOps/insights-docs/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"vuepress-plugin-check-md": "0.0.2"
|
||||
},
|
||||
"description": "A repository with a Vuepress template for Fairwinds projects",
|
||||
"devDependencies": {
|
||||
"vuepress": "^1.4.0",
|
||||
"vuepress-plugin-clean-urls": "^1.1.1",
|
||||
"vuepress-plugin-redirect": "^1.2.3"
|
||||
},
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
"homepage": "https://github.com/FairwindsOps/insights-docs#readme",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"name": "fairwinds-docs-template",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/FairwindsOps/insights-docs.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:readme && npm run build:docs",
|
||||
"build:docs": "vuepress build -d ../dist/",
|
||||
"build: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"
|
||||
},
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
# Installation and Usage
|
||||
Polaris can be installed on your cluster using kubectl or Helm. It can also
|
||||
be run as a local binary, which will use your kubeconfig to connect to the cluster
|
||||
or run against local YAML files.
|
||||
|
||||
## Configuration
|
||||
Polaris supports a wide range of validations covering a number of Kubernetes best practices.
|
||||
Here's a [sample configuration file](/examples/config-full.yaml) that includes all currently supported checks.
|
||||
The [default configuration](/examples/config.yaml) contains a number of those checks.
|
||||
|
||||
|
||||
### Checks
|
||||
Each check can be assigned a `severity`. Only checks with a severity of `danger` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `danger` will result in a change being rejected.
|
||||
|
||||
Polaris validation checks fall into several different categories:
|
||||
|
||||
- [Health Checks](check-documentation/health-checks.md)
|
||||
- [Images](check-documentation/images.md)
|
||||
- [Networking](check-documentation/networking.md)
|
||||
- [Resources](check-documentation/resources.md)
|
||||
- [Security](check-documentation/security.md)
|
||||
|
||||
#### Custom Checks
|
||||
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/). For example,
|
||||
to disallow images from quay.io:
|
||||
|
||||
```yaml
|
||||
checks:
|
||||
imageRegistry: warning
|
||||
customChecks:
|
||||
imageRegistry:
|
||||
successMessage: Image comes from allowed registries
|
||||
failureMessage: Image should not be from disallowed registry
|
||||
category: Images
|
||||
target: Container # target can be "Container" or "Pod"
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
type: string
|
||||
not:
|
||||
pattern: ^quay.io
|
||||
```
|
||||
|
||||
Schemas can also be specified as JSON strings instead of YAML, for easier copy/pasting:
|
||||
```yaml
|
||||
customChecks:
|
||||
foo:
|
||||
jsonSchema: |
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
|
||||
strings like `1000m` and `1G`. You can see an example in [the extended config](/examples/config-full.yaml)
|
||||
|
||||
There are additional examples in the [checks folder](/checks).
|
||||
|
||||
### Exemptions
|
||||
Sometimes a workload really does need to do things that Polaris considers insecure. For instance,
|
||||
many of the `kube-system` workloads need to run as root, or need access to the host network. In these
|
||||
cases, we can add **exemptions** to allow the workload to pass Polaris checks.
|
||||
|
||||
Exemptions can be added two ways: by annotating a controller, or editing the Polaris config.
|
||||
|
||||
To exempt a controller from all checks via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true
|
||||
```
|
||||
|
||||
To exempt a controller via the config, you have to specify a list of controller names and a list of rules, e.g.
|
||||
```yaml
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- dns-controller
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
```
|
||||
|
||||
To exempt a controller from a particular check via annotations, use an annotation in the form of `polaris.fairwinds.com/<check>-exempt=true`, e.g.
|
||||
```
|
||||
kubectl annotate deployment my-deployment polaris.fairwinds.com/cpuRequestsMissing-exempt=true
|
||||
```
|
||||
|
||||
# Installing
|
||||
There are several ways to install and use Polaris. Below outline ways to install using `kubectl`, `helm` and `local binary`.
|
||||
|
||||
## Dashboard
|
||||
The dashboard can be installed on a cluster using kubectl or Helm. It can also be run locally,
|
||||
connecting to your cluster using the credentials stored in your `KUBECONFIG`.
|
||||
|
||||
### kubectl
|
||||
```bash
|
||||
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/dashboard.yaml
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
|
||||
### Local Binary
|
||||
You'll need a valid `KUBECONFIG` set up for the dashboard to connect to your cluster.
|
||||
|
||||
Binary releases can be dowloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap reactiveops/tap
|
||||
brew install reactiveops/tap/polaris
|
||||
polaris dashboard --port 8080
|
||||
```
|
||||
|
||||
You can also point the dashboard to the local filesystem, instead of a live cluster:
|
||||
```bash
|
||||
polaris dashboard --port 8080 --audit-path=./deploy/
|
||||
```
|
||||
|
||||
### Local Docker container
|
||||
|
||||
```
|
||||
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro quay.io/fairwinds/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
|
||||
```
|
||||
|
||||
## Webhook
|
||||
### kubectl
|
||||
```bash
|
||||
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/webhook.yaml
|
||||
```
|
||||
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwindsops-stable https://charts.fairwindsops.com/stable
|
||||
helm upgrade --install polaris fairwindsops-stable/polaris --namespace polaris \
|
||||
--set webhook.enable=true --set dashboard.enable=false
|
||||
```
|
||||
|
||||
## CLI
|
||||
### Installation
|
||||
Binary releases can be downloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
|
||||
or can be installed with [Homebrew](https://brew.sh/):
|
||||
```bash
|
||||
brew tap FairwindsOps/tap
|
||||
brew install FairwindsOps/tap/polaris
|
||||
polaris version
|
||||
```
|
||||
|
||||
You can run audits on the command line and see the output as JSON, YAML, or a raw score:
|
||||
```bash
|
||||
polaris audit --format yaml > report.yaml
|
||||
polaris audit --format score
|
||||
# 92
|
||||
```
|
||||
|
||||
Audits can run against a local directory or YAML file rather than a cluster:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/
|
||||
|
||||
# or to use STDIN
|
||||
cat pod.yaml | polaris audit --audit-path -
|
||||
```
|
||||
|
||||
You can also run the audit on a single resource instead of the entire cluster:
|
||||
```bash
|
||||
polaris audit --resource "nginx-ingress/Deployment.apps/v1/default-backend"
|
||||
```
|
||||
|
||||
#### Running with CI/CD
|
||||
You can integrate Polaris into CI/CD for repositories containing infrastructure-as-code.
|
||||
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--set-exit-code-on-danger \
|
||||
--set-exit-code-below-score 90
|
||||
```
|
||||
|
||||
For more on exit code meanings, see [exit-code docs](exit-codes.md).
|
||||
|
||||
#### CLI Options
|
||||
|
||||
```
|
||||
# top-level commands
|
||||
audit
|
||||
Runs a one-time audit.
|
||||
dashboard
|
||||
Runs the webserver for Polaris dashboard.
|
||||
help
|
||||
Prints help, if you give it a command then it will print help for that command. Same as -h
|
||||
version
|
||||
Prints the version of Polaris
|
||||
webhook
|
||||
Runs the webhook webserver
|
||||
|
||||
# high-level flags
|
||||
-c, --config string
|
||||
Location of Polaris configuration file
|
||||
--disallow-exemptions
|
||||
Disallow any exemptions from configuration file.
|
||||
-h, --help
|
||||
Help for Polaris (same as help command)
|
||||
--kubeconfig string
|
||||
Path to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string
|
||||
Logrus log level (default "info")
|
||||
--master string
|
||||
The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.
|
||||
|
||||
# dashboard flags
|
||||
--audit-path string
|
||||
If specified, audits one or more YAML files instead of a cluster
|
||||
--base-path string
|
||||
Path on which the dashboard is served (default "/")
|
||||
--display-name string
|
||||
An optional identifier for the audit
|
||||
--load-audit-file string
|
||||
Runs the dashboard with data saved from a past audit.
|
||||
-p, --port int
|
||||
Port for the dashboard webserver (default 8080)
|
||||
|
||||
# audit flags
|
||||
--audit-path string
|
||||
If specified, audits one or more YAML files instead of a cluster
|
||||
--resource string
|
||||
If specified, audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend
|
||||
--display-name string
|
||||
An optional identifier for the audit
|
||||
--format string
|
||||
Output format for results - json, yaml, or score (default "json")
|
||||
--output-file string
|
||||
Destination file for audit results
|
||||
--output-url string
|
||||
Destination URL to send audit results
|
||||
--set-exit-code-below-score int
|
||||
Set an exit code of 4 when the score is below this threshold (1-100)
|
||||
--set-exit-code-on-danger
|
||||
Set an exit code of 3 when the audit contains danger-level issues.
|
||||
|
||||
# webhook flags
|
||||
--disable-webhook-config-installer
|
||||
disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
|
||||
-p, --port int
|
||||
Port for the webhook webserver (default 9876)
|
||||
```
|
||||
@@ -2,20 +2,15 @@ checks:
|
||||
# reliability
|
||||
multipleReplicasForDeployment: warning
|
||||
priorityClassNotSet: warning
|
||||
# resources
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
# efficiency
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
# images
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
# healthChecks
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
# networking
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
# security
|
||||
hostIPCSet: danger
|
||||
hostPIDSet: danger
|
||||
@@ -25,6 +20,8 @@ checks:
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
# custom
|
||||
resourceLimits: warning
|
||||
imageRegistry: danger
|
||||
@@ -35,6 +32,12 @@ exemptions:
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
- hostPortSet
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- my-network-controller
|
||||
rules:
|
||||
- hostNetworkSet
|
||||
- hostPortSet
|
||||
|
||||
customChecks:
|
||||
resourceLimits:
|
||||
|
||||
@@ -2,20 +2,19 @@ checks:
|
||||
# reliability
|
||||
multipleReplicasForDeployment: ignore
|
||||
priorityClassNotSet: ignore
|
||||
# resources
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
metadataAndNameMismatched: ignore
|
||||
pdbDisruptionsIsZero: warning
|
||||
missingPodDisruptionBudget: ignore
|
||||
|
||||
# efficiency
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
# images
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
# healthChecks
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
# networking
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
# security
|
||||
hostIPCSet: danger
|
||||
hostPIDSet: danger
|
||||
@@ -25,9 +24,13 @@ checks:
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
tlsSettingsMissing: warning
|
||||
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- kube-apiserver
|
||||
- kube-proxy
|
||||
- kube-scheduler
|
||||
|
||||
@@ -7,24 +7,19 @@
|
||||
"ClusterInfo": {
|
||||
"Version": "unknown",
|
||||
"Nodes": 0,
|
||||
"Pods": 0,
|
||||
"Namespaces": 2,
|
||||
"Deployments": 2,
|
||||
"StatefulSets": 0,
|
||||
"DaemonSets": 0,
|
||||
"Jobs": 0,
|
||||
"CronJobs": 0,
|
||||
"ReplicationControllers": 0
|
||||
"Pods": 5,
|
||||
"Namespaces": 3,
|
||||
"Controllers": 5
|
||||
},
|
||||
"Results": [
|
||||
{
|
||||
"Name": "polaris-dashboard",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Deployment",
|
||||
"Messages": {},
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
@@ -50,7 +45,7 @@
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "dashboard",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
@@ -145,16 +140,17 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-webhook",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Deployment",
|
||||
"Messages": {},
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
@@ -180,7 +176,7 @@
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook",
|
||||
"Messages": {
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
@@ -275,7 +271,373 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-certificate-updater",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Job",
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostNetworkSet": {
|
||||
"ID": "hostNetworkSet",
|
||||
"Message": "Host network is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"hostPIDSet": {
|
||||
"ID": "hostPIDSet",
|
||||
"Message": "Host PID is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
}
|
||||
},
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook-certificate-generator",
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"cpuRequestsMissing": {
|
||||
"ID": "cpuRequestsMissing",
|
||||
"Message": "CPU requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"dangerousCapabilities": {
|
||||
"ID": "dangerousCapabilities",
|
||||
"Message": "Container does not have any dangerous capabilities",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostPortSet": {
|
||||
"ID": "hostPortSet",
|
||||
"Message": "Host port is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"insecureCapabilities": {
|
||||
"ID": "insecureCapabilities",
|
||||
"Message": "Container does not have any insecure capabilities",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"memoryLimitsMissing": {
|
||||
"ID": "memoryLimitsMissing",
|
||||
"Message": "Memory limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"memoryRequestsMissing": {
|
||||
"ID": "memoryRequestsMissing",
|
||||
"Message": "Memory requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"privilegeEscalationAllowed": {
|
||||
"ID": "privilegeEscalationAllowed",
|
||||
"Message": "Privilege escalation not allowed",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsPrivileged": {
|
||||
"ID": "runAsPrivileged",
|
||||
"Message": "Not running as privileged",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsRootAllowed": {
|
||||
"ID": "runAsRootAllowed",
|
||||
"Message": "Is not allowed to run as root",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"tagNotSpecified": {
|
||||
"ID": "tagNotSpecified",
|
||||
"Message": "Image tag is specified",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Images"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-webhook",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Deployment",
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostNetworkSet": {
|
||||
"ID": "hostNetworkSet",
|
||||
"Message": "Host network is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"hostPIDSet": {
|
||||
"ID": "hostPIDSet",
|
||||
"Message": "Host PID is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
}
|
||||
},
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook",
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"cpuRequestsMissing": {
|
||||
"ID": "cpuRequestsMissing",
|
||||
"Message": "CPU requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"dangerousCapabilities": {
|
||||
"ID": "dangerousCapabilities",
|
||||
"Message": "Container does not have any dangerous capabilities",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostPortSet": {
|
||||
"ID": "hostPortSet",
|
||||
"Message": "Host port is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"insecureCapabilities": {
|
||||
"ID": "insecureCapabilities",
|
||||
"Message": "Container does not have any insecure capabilities",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"livenessProbeMissing": {
|
||||
"ID": "livenessProbeMissing",
|
||||
"Message": "Liveness probe is configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Health Checks"
|
||||
},
|
||||
"memoryLimitsMissing": {
|
||||
"ID": "memoryLimitsMissing",
|
||||
"Message": "Memory limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"memoryRequestsMissing": {
|
||||
"ID": "memoryRequestsMissing",
|
||||
"Message": "Memory requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"privilegeEscalationAllowed": {
|
||||
"ID": "privilegeEscalationAllowed",
|
||||
"Message": "Privilege escalation not allowed",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"readinessProbeMissing": {
|
||||
"ID": "readinessProbeMissing",
|
||||
"Message": "Readiness probe is configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Health Checks"
|
||||
},
|
||||
"runAsPrivileged": {
|
||||
"ID": "runAsPrivileged",
|
||||
"Message": "Not running as privileged",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsRootAllowed": {
|
||||
"ID": "runAsRootAllowed",
|
||||
"Message": "Is not allowed to run as root",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"tagNotSpecified": {
|
||||
"ID": "tagNotSpecified",
|
||||
"Message": "Image tag is specified",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Images"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"Name": "polaris-certificate-updater",
|
||||
"Namespace": "polaris",
|
||||
"Kind": "Job",
|
||||
"Results": {},
|
||||
"PodResult": {
|
||||
"Name": "",
|
||||
"Results": {
|
||||
"hostIPCSet": {
|
||||
"ID": "hostIPCSet",
|
||||
"Message": "Host IPC is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostNetworkSet": {
|
||||
"ID": "hostNetworkSet",
|
||||
"Message": "Host network is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"hostPIDSet": {
|
||||
"ID": "hostPIDSet",
|
||||
"Message": "Host PID is not configured",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
}
|
||||
},
|
||||
"ContainerResults": [
|
||||
{
|
||||
"Name": "webhook-certificate-generator",
|
||||
"Results": {
|
||||
"cpuLimitsMissing": {
|
||||
"ID": "cpuLimitsMissing",
|
||||
"Message": "CPU limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"cpuRequestsMissing": {
|
||||
"ID": "cpuRequestsMissing",
|
||||
"Message": "CPU requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"dangerousCapabilities": {
|
||||
"ID": "dangerousCapabilities",
|
||||
"Message": "Container does not have any dangerous capabilities",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"hostPortSet": {
|
||||
"ID": "hostPortSet",
|
||||
"Message": "Host port is not configured",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Networking"
|
||||
},
|
||||
"insecureCapabilities": {
|
||||
"ID": "insecureCapabilities",
|
||||
"Message": "Container does not have any insecure capabilities",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"memoryLimitsMissing": {
|
||||
"ID": "memoryLimitsMissing",
|
||||
"Message": "Memory limits are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"memoryRequestsMissing": {
|
||||
"ID": "memoryRequestsMissing",
|
||||
"Message": "Memory requests are set",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Resources"
|
||||
},
|
||||
"privilegeEscalationAllowed": {
|
||||
"ID": "privilegeEscalationAllowed",
|
||||
"Message": "Privilege escalation not allowed",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsPrivileged": {
|
||||
"ID": "runAsPrivileged",
|
||||
"Message": "Not running as privileged",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Security"
|
||||
},
|
||||
"runAsRootAllowed": {
|
||||
"ID": "runAsRootAllowed",
|
||||
"Message": "Is not allowed to run as root",
|
||||
"Success": true,
|
||||
"Severity": "warning",
|
||||
"Category": "Security"
|
||||
},
|
||||
"tagNotSpecified": {
|
||||
"ID": "tagNotSpecified",
|
||||
"Message": "Image tag is specified",
|
||||
"Success": true,
|
||||
"Severity": "danger",
|
||||
"Category": "Images"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreatedTime": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
options:
|
||||
organization: fairwinds-opensource
|
||||
baseBranch: master
|
||||
|
||||
# These images will be scanned for vulnerabilities.
|
||||
images:
|
||||
docker:
|
||||
- quay.io/fairwinds/polaris:$CI_SHA1
|
||||
|
||||
# These manifests will be scanned for configuration issues.
|
||||
manifests:
|
||||
yaml:
|
||||
- ./deploy/dashboard.yaml
|
||||
- ./deploy/webhook.yaml
|
||||
@@ -1,40 +1,25 @@
|
||||
module github.com/fairwindsops/polaris
|
||||
|
||||
go 1.13
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.65.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.4 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.2 // indirect
|
||||
github.com/gobuffalo/packr/v2 v2.8.0
|
||||
github.com/google/go-cmp v0.5.2 // indirect
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/gobuffalo/packr/v2 v2.8.1
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/gophercloud/gophercloud v0.12.0 // indirect
|
||||
github.com/google/uuid v1.1.3 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/karrick/godirwalk v1.16.1 // indirect
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.13.0 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 // indirect
|
||||
gitlab.com/golang-commonmark/markdown v0.0.0-20191127184510-91b5b3c99c19
|
||||
go.uber.org/zap v1.16.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||
golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
k8s.io/api v0.18.8
|
||||
k8s.io/apimachinery v0.18.8
|
||||
k8s.io/client-go v0.18.6
|
||||
k8s.io/klog/v2 v2.1.0 // indirect
|
||||
k8s.io/utils v0.0.0-20200821003339-5e75c0163111 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.6.3
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/thoas/go-funk v0.9.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.22.0
|
||||
k8s.io/apimachinery v0.22.0
|
||||
k8s.io/client-go v0.22.0
|
||||
sigs.k8s.io/controller-runtime v0.9.6
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "1.2.1"
|
||||
Version = "4.0.8"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// BuiltInChecks contains the checks that come pre-installed w/ Polaris
|
||||
BuiltInChecks = map[string]SchemaCheck{}
|
||||
schemaBox = (*packr.Box)(nil)
|
||||
// We explicitly set the order to avoid thrash in the
|
||||
// tests as we migrate toward JSON schema
|
||||
checkOrder = []string{
|
||||
// Controller Checks
|
||||
"multipleReplicasForDeployment",
|
||||
// Pod checks
|
||||
"hostIPCSet",
|
||||
"hostPIDSet",
|
||||
"hostNetworkSet",
|
||||
// Container checks
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
"cpuLimitsMissing",
|
||||
"cpuRequestsMissing",
|
||||
"readinessProbeMissing",
|
||||
"livenessProbeMissing",
|
||||
"pullPolicyNotAlways",
|
||||
"tagNotSpecified",
|
||||
"hostPortSet",
|
||||
"runAsRootAllowed",
|
||||
"runAsPrivileged",
|
||||
"notReadOnlyRootFilesystem",
|
||||
"privilegeEscalationAllowed",
|
||||
"dangerousCapabilities",
|
||||
"insecureCapabilities",
|
||||
"priorityClassNotSet",
|
||||
// Other checks
|
||||
"tlsSettingsMissing",
|
||||
"pdbDisruptionsIsZero",
|
||||
"metadataAndNameMismatched",
|
||||
"missingPodDisruptionBudget",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
schemaBox = packr.New("Schemas", "../../checks")
|
||||
for _, checkID := range checkOrder {
|
||||
contents, err := schemaBox.Find(checkID + ".yaml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
check, err := ParseCheck(checkID, contents)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while parsing check %s", checkID)
|
||||
panic(err)
|
||||
}
|
||||
BuiltInChecks[checkID] = check
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
packr "github.com/gobuffalo/packr/v2"
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
@@ -40,6 +40,8 @@ type Configuration struct {
|
||||
type Exemption struct {
|
||||
Rules []string `json:"rules"`
|
||||
ControllerNames []string `json:"controllerNames"`
|
||||
ContainerNames []string `json:"containerNames"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
var configBox = (*packr.Box)(nil)
|
||||
@@ -58,14 +60,14 @@ func ParseFile(path string) (Configuration, error) {
|
||||
if path == "" {
|
||||
rawBytes, err = getConfigBox().Find("config.yaml")
|
||||
} else if strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://") {
|
||||
//path is a url
|
||||
// path is a url
|
||||
response, err2 := http.Get(path)
|
||||
if err2 != nil {
|
||||
return Configuration{}, err2
|
||||
}
|
||||
rawBytes, err = ioutil.ReadAll(response.Body)
|
||||
} else {
|
||||
//path is local
|
||||
// path is local
|
||||
rawBytes, err = ioutil.ReadFile(path)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -101,8 +103,8 @@ func Parse(rawBytes []byte) (Configuration, error) {
|
||||
}
|
||||
|
||||
// Validate checks if a config is valid
|
||||
func (c Configuration) Validate() error {
|
||||
if len(c.Checks) == 0 {
|
||||
func (conf Configuration) Validate() error {
|
||||
if len(conf.Checks) == 0 {
|
||||
return errors.New("No checks were enabled")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -16,6 +16,7 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -142,7 +143,9 @@ func TestConfigNoServerError(t *testing.T) {
|
||||
|
||||
func TestConfigWithCustomChecks(t *testing.T) {
|
||||
valid := map[string]interface{}{
|
||||
"securityContext": map[string]interface{}{},
|
||||
"securityContext": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
invalid := map[string]interface{}{
|
||||
"notSecurityContext": map[string]interface{}{},
|
||||
@@ -151,20 +154,23 @@ func TestConfigWithCustomChecks(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confCustomChecks))
|
||||
assert.NoError(t, err, "Expected no error when parsing YAML config")
|
||||
assert.Equal(t, 1, len(parsedConf.CustomChecks))
|
||||
isValid, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]interface{}{})
|
||||
isValid, _, err := check.CheckObject(valid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
|
||||
parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
|
||||
assert.NoError(t, err, "Expected no error when parsing YAML config")
|
||||
assert.Equal(t, 1, len(parsedConf.CustomChecks))
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
|
||||
if !assert.Equal(t, true, isValid) {
|
||||
fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
|
||||
}
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
}
|
||||
|
||||
@@ -2,35 +2,53 @@ package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// IsActionable determines whether a check is actionable given the current configuration
|
||||
func (conf Configuration) IsActionable(ruleID, controllerName string) bool {
|
||||
func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, containerName string) bool {
|
||||
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
|
||||
return false
|
||||
}
|
||||
if conf.DisallowExemptions {
|
||||
return true
|
||||
}
|
||||
for _, exemption := range conf.Exemptions {
|
||||
if exemption.Namespace != "" && exemption.Namespace != objMeta.GetNamespace() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, example := range conf.Exemptions {
|
||||
for _, rule := range example.Rules {
|
||||
checkIfRuleMatches := false
|
||||
for _, rule := range exemption.Rules {
|
||||
if rule != ruleID {
|
||||
continue
|
||||
}
|
||||
for _, controller := range example.ControllerNames {
|
||||
if strings.HasPrefix(controllerName, controller) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
checkIfRuleMatches = true
|
||||
break
|
||||
}
|
||||
if len(example.Rules) == 0 {
|
||||
for _, controller := range example.ControllerNames {
|
||||
if strings.HasPrefix(controllerName, controller) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(exemption.Rules) == 0 || checkIfRuleMatches {
|
||||
if !isExemptionCheckMatched(exemption.ControllerNames, objMeta.GetName()) {
|
||||
continue
|
||||
}
|
||||
if isExemptionCheckMatched(exemption.ContainerNames, containerName) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isExemptionCheckMatched(arr []string, predicate string) bool {
|
||||
if len(arr) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, container := range arr {
|
||||
if strings.HasPrefix(predicate, container) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -18,45 +18,248 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
var confExemptRuleTest = `
|
||||
var confContainerTest = `
|
||||
checks:
|
||||
ANY: warning
|
||||
OTHER: warning
|
||||
multipleReplicasForDeployment: warning
|
||||
priorityClassNotSet: warning
|
||||
pullPolicyNotAlways: warning
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- test
|
||||
- namespace: prometheus
|
||||
rules:
|
||||
- ANY
|
||||
`
|
||||
|
||||
var confExemptTest = `
|
||||
checks:
|
||||
ANY: warning
|
||||
exemptions:
|
||||
- multipleReplicasForDeployment
|
||||
- controllerNames:
|
||||
- controller2
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- controller3
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- containerNames:
|
||||
- container41
|
||||
- container42
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- namespace: kube-system
|
||||
containerNames:
|
||||
- container51
|
||||
- container52
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- controllerNames:
|
||||
- test
|
||||
- controller6
|
||||
containerNames:
|
||||
- container61
|
||||
- container62
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- controller7
|
||||
containerNames:
|
||||
- container71
|
||||
- container72
|
||||
rules:
|
||||
- multipleReplicasForDeployment
|
||||
- priorityClassNotSet
|
||||
- namespace: polaris
|
||||
`
|
||||
|
||||
func TestInclusiveExemption(t *testing.T) {
|
||||
parsedConf, _ := Parse([]byte(confExemptTest))
|
||||
applicable := parsedConf.IsActionable("ANY", "test")
|
||||
applicableOtherController := parsedConf.IsActionable("ANY", "other")
|
||||
|
||||
assert.False(t, applicable, "Expected all checks to be exempted when their controller is specified.")
|
||||
assert.True(t, applicableOtherController, "Expected checks to only be exempted when their controller is specified.")
|
||||
func createMeta(namespace, name string) metav1.Object {
|
||||
unst := unstructured.Unstructured{}
|
||||
obj, err := meta.Accessor(&unst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
obj.SetName(name)
|
||||
obj.SetNamespace(namespace)
|
||||
return obj
|
||||
}
|
||||
|
||||
func TestIndividualRuleException(t *testing.T) {
|
||||
parsedConf, _ := Parse([]byte(confExemptRuleTest))
|
||||
applicable := parsedConf.IsActionable("ANY", "test")
|
||||
applicableOtherRule := parsedConf.IsActionable("OTHER", "test")
|
||||
applicableOtherRuleOtherController := parsedConf.IsActionable("OTHER", "other")
|
||||
applicableRuleOtherController := parsedConf.IsActionable("ANY", "other")
|
||||
func TestNamespaceExemptionForSpecifiedRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.False(t, applicable, "Expected all checks to be exempted when their controller and rule are specified.")
|
||||
assert.True(t, applicableOtherRule, "Expected checks to only be exempted when their controller and rule are specified.")
|
||||
assert.True(t, applicableOtherRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
|
||||
assert.True(t, applicableRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", 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", ""), "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
func TestNamespaceExemptionForAllRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("polaris", "controller1"), "")
|
||||
assert.False(t, actionable)
|
||||
}
|
||||
|
||||
func TestControllerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller3"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "container31")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
func TestOnlyContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container42")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller4"), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
func TestNamespaceAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", 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")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller5"), "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
func TestControllerAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", 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")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container61")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
func TestContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container71")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", "controller7"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", "controller7"), "container71")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("kube-system", "controller8"), "container71")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -1,43 +1,86 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/qri-io/jsonschema"
|
||||
"github.com/thoas/go-funk"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
// TargetKind represents the part of the config to be validated
|
||||
type TargetKind string
|
||||
|
||||
const (
|
||||
// TargetController points to the controller's spec
|
||||
TargetController TargetKind = "Controller"
|
||||
// TargetContainer points to the container spec
|
||||
TargetContainer TargetKind = "Container"
|
||||
// TargetPod points to the pod spec
|
||||
TargetPod TargetKind = "Pod"
|
||||
// TargetController points to the controller's spec
|
||||
TargetController TargetKind = "Controller"
|
||||
)
|
||||
|
||||
// HandledTargets is a list of target names that are explicitly handled
|
||||
var HandledTargets = []TargetKind{
|
||||
TargetController,
|
||||
TargetContainer,
|
||||
TargetPod,
|
||||
}
|
||||
|
||||
// SchemaCheck is a Polaris check that runs using JSON Schema
|
||||
type SchemaCheck struct {
|
||||
ID string `yaml:"id"`
|
||||
Category string `yaml:"category"`
|
||||
SuccessMessage string `yaml:"successMessage"`
|
||||
FailureMessage string `yaml:"failureMessage"`
|
||||
Controllers includeExcludeList `yaml:"controllers"`
|
||||
Containers includeExcludeList `yaml:"containers"`
|
||||
Target TargetKind `yaml:"target"`
|
||||
SchemaTarget TargetKind `yaml:"schemaTarget"`
|
||||
Schema jsonschema.RootSchema `yaml:"schema"`
|
||||
JSONSchema string `yaml:"jsonSchema"`
|
||||
ID string `yaml:"id" json:"id"`
|
||||
Category string `yaml:"category" json:"category"`
|
||||
SuccessMessage string `yaml:"successMessage" json:"successMessage"`
|
||||
FailureMessage string `yaml:"failureMessage" json:"failureMessage"`
|
||||
Controllers includeExcludeList `yaml:"controllers" json:"controllers"`
|
||||
Containers includeExcludeList `yaml:"containers" json:"containers"`
|
||||
Target TargetKind `yaml:"target" json:"target"`
|
||||
SchemaTarget TargetKind `yaml:"schemaTarget" json:"schemaTarget"`
|
||||
Schema map[string]interface{} `yaml:"schema" json:"schema"`
|
||||
SchemaString string `yaml:"schemaString" json:"schemaString"`
|
||||
Validator jsonschema.RootSchema `yaml:"-" json:"-"`
|
||||
AdditionalSchemas map[string]map[string]interface{} `yaml:"additionalSchemas" json:"additionalSchemas"`
|
||||
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
|
||||
AdditionalValidators map[string]jsonschema.RootSchema `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
type resourceMinimum string
|
||||
type resourceMaximum string
|
||||
|
||||
func unmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
|
||||
reader := bytes.NewReader(raw)
|
||||
d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
|
||||
for {
|
||||
if err := d.Decode(dest); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("Decoding schema check failed: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseCheck parses a check from a byte array
|
||||
func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
|
||||
check := SchemaCheck{}
|
||||
err := unmarshalYAMLOrJSON(rawBytes, &check)
|
||||
if err != nil {
|
||||
return check, err
|
||||
}
|
||||
check.Initialize(id)
|
||||
return check, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
|
||||
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
|
||||
@@ -117,48 +160,136 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
|
||||
// Initialize sets up the schema
|
||||
func (check *SchemaCheck) Initialize(id string) error {
|
||||
check.ID = id
|
||||
if check.JSONSchema != "" {
|
||||
if err := json.Unmarshal([]byte(check.JSONSchema), &check.Schema); err != nil {
|
||||
if check.SchemaString == "" {
|
||||
jsonBytes, err := json.Marshal(check.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
check.SchemaString = string(jsonBytes)
|
||||
}
|
||||
if check.AdditionalSchemaStrings == nil {
|
||||
check.AdditionalSchemaStrings = make(map[string]string)
|
||||
}
|
||||
for kind, schema := range check.AdditionalSchemas {
|
||||
jsonBytes, err := json.Marshal(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
check.AdditionalSchemaStrings[kind] = string(jsonBytes)
|
||||
}
|
||||
check.Schema = map[string]interface{}{}
|
||||
check.AdditionalSchemas = map[string]map[string]interface{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TemplateForResource fills out a check's templated fields given a particular resource
|
||||
func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, error) {
|
||||
newCheck := check // Make a copy of the check, since we're going to modify the schema
|
||||
|
||||
templateStrings := map[string]string{
|
||||
"": newCheck.SchemaString,
|
||||
}
|
||||
for kind, schema := range newCheck.AdditionalSchemaStrings {
|
||||
templateStrings[kind] = schema
|
||||
}
|
||||
newCheck.SchemaString = ""
|
||||
newCheck.AdditionalSchemaStrings = map[string]string{}
|
||||
|
||||
for kind, tmplString := range templateStrings {
|
||||
tmpl := template.New(newCheck.ID)
|
||||
tmpl, err := tmpl.Parse(tmplString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := bytes.Buffer{}
|
||||
err = tmpl.Execute(&w, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if kind == "" {
|
||||
newCheck.SchemaString = w.String()
|
||||
} else {
|
||||
newCheck.AdditionalSchemaStrings[kind] = w.String()
|
||||
}
|
||||
}
|
||||
|
||||
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
|
||||
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
|
||||
val := jsonschema.RootSchema{}
|
||||
err := unmarshalYAMLOrJSON([]byte(schemaStr), &val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newCheck.AdditionalValidators[kind] = val
|
||||
}
|
||||
err := unmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newCheck, err
|
||||
}
|
||||
|
||||
// CheckPod checks a pod spec against the schema
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(pod)
|
||||
}
|
||||
|
||||
// CheckController checks a controler's spec against the schema
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, error) {
|
||||
errs, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errs) == 0, err
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
}
|
||||
|
||||
// CheckContainer checks a container spec against the schema
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(container)
|
||||
}
|
||||
|
||||
// CheckObject checks arbitrary data against the schema
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, nil, err
|
||||
}
|
||||
errs, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errs) == 0, err
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
}
|
||||
|
||||
// CheckAdditionalObjects looks for an object that passes the specified additional schema
|
||||
func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []interface{}) (bool, error) {
|
||||
val, ok := check.AdditionalValidators[groupkind]
|
||||
if !ok {
|
||||
return false, errors.New("No validator found for " + groupkind)
|
||||
}
|
||||
for _, obj := range objects {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
errs, err := val.ValidateBytes(bytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsActionable decides if this check applies to a particular target
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool {
|
||||
if check.Target != target {
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
|
||||
if funk.Contains(HandledTargets, target) {
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
} else if string(check.Target) != kind && !strings.HasSuffix(string(check.Target), "/"+kind) {
|
||||
return false
|
||||
}
|
||||
isIncluded := len(check.Controllers.Include) == 0
|
||||
for _, inclusion := range check.Controllers.Include {
|
||||
if inclusion == controllerType {
|
||||
if inclusion == kind {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
@@ -167,7 +298,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, controllerType string,
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Controllers.Exclude {
|
||||
if exclusion == controllerType {
|
||||
if exclusion == kind {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,27 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card.insights {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #20162D;
|
||||
background-color: #CACBD2;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights a {
|
||||
background-color: #20162D;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
.filters .title,
|
||||
.card h3 {
|
||||
margin: 0;
|
||||
@@ -359,4 +380,4 @@ form.namespace-list {
|
||||
.namespace-row label {
|
||||
margin-left: 4px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer .row {
|
||||
margin: 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #200C36;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.footer > div {
|
||||
width: 200px;
|
||||
}
|
||||
@@ -5,34 +5,11 @@ body {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.navbar, .main-content {
|
||||
.main-content {
|
||||
width: 960px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.navbar .navbar-content {
|
||||
padding: 0px 20px;
|
||||
}
|
||||
|
||||
.navbar .navbar-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.navbar .logo {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.navbar .fw-logo {
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 25px 20px;
|
||||
padding: 20px;
|
||||
@@ -42,26 +19,8 @@ body {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.exemption-alert {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
border: 1px solid #f26c21;
|
||||
border-radius: 2px;
|
||||
padding: 30px 0 15px;
|
||||
}
|
||||
.exemption-alert .fa-exclamation {
|
||||
margin-right: 10px;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
.navbar {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar .logo {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.navbar .fw-logo {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navbar .fw-nav,
|
||||
.navbar .polaris-nav {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar .fw-nav {
|
||||
background-color: #180929;
|
||||
}
|
||||
|
||||
.navbar .fw-nav a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.navbar .fw-nav .right-section img {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.navbar .polaris-nav {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.navbar .polaris-nav a {
|
||||
color: #180929;
|
||||
}
|
||||
|
||||
.navbar .polaris-nav .dropdown-toggle:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.navbar .polaris-nav .dropdown-menu {
|
||||
max-height: 9rem;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.navbar .right-section > a {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="576" height="185" viewBox="0 0 576 185" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.400391 184.199V50.5994H18.5004V65.4994C23.7004 55.9994 35.0004 48.3994 50.6004 48.3994C79.3004 48.3994 94.6004 70.4994 94.6004 98.3994C94.6004 126.899 78.0004 148.999 50.0004 148.999C35.3004 148.999 24.1004 141.999 18.9004 133.499V184.299H0.400391V184.199ZM47.2004 64.9994C30.1004 64.9994 18.7004 79.2994 18.7004 98.4994C18.7004 118.199 30.0004 132.299 47.2004 132.299C64.5004 132.299 75.5004 118.199 75.5004 98.4994C75.5004 79.2994 64.7004 64.9994 47.2004 64.9994Z" fill="#23183A"/>
|
||||
<path d="M208.5 98.3996C208.5 127.7 188.2 149.4 159.5 149.4C130.8 149.4 110.5 127.7 110.5 98.3996C110.5 69.2996 130.8 47.5996 159.5 47.5996C188.2 47.5996 208.5 69.2996 208.5 98.3996ZM189.4 98.3996C189.4 76.2996 175.5 64.3996 159.5 64.3996C143.6 64.3996 129.6 76.2996 129.6 98.3996C129.6 120.7 143.5 132.6 159.5 132.6C175.4 132.7 189.4 120.7 189.4 98.3996Z" fill="#23183A"/>
|
||||
<path d="M232.4 146.4V2.2002H251.1V146.4H232.4V146.4Z" fill="#23183A"/>
|
||||
<path d="M306 91.5996L331.9 87.7996C337.7 86.9996 339.3 83.9996 339.3 80.5996C339.3 71.1996 333.1 63.4996 318.6 63.4996C305.5 63.4996 298.1 71.6996 296.9 82.7996L279 78.6996C281 60.3996 297.5 47.5996 318.2 47.5996C346.9 47.5996 358 63.8996 358 82.5996V130.6C358 139 358.8 143.9 359.2 146.3H340.9C340.5 143.9 339.9 140.3 339.9 133.4C335.7 140.2 326.2 149.3 309 149.3C289.5 149.3 276.9 135.8 276.9 120.6C277 103.6 289.5 93.9996 306 91.5996ZM339.3 105.4V101L310 105.4C302 106.6 296.1 111.2 296.1 119.7C296.1 126.9 302.1 133.6 311.6 133.6C326.9 133.7 339.3 126.3 339.3 105.4Z" fill="#23183A"/>
|
||||
<path d="M440.2 69.5C437.4 69.1 434.6 68.9 432 68.9C416.3 68.9 405.7 77.3 405.7 98.4V146.4H387V50.6H405.3V67.3C412.3 52.6 424.2 49 434 49C436.6 49 439.2 49.4 440.2 49.6V69.5Z" fill="#23183A"/>
|
||||
<path d="M469.3 0.799805C476.9 0.799805 482.8 6.7998 482.8 14.2998C482.8 21.6998 476.8 27.5998 469.3 27.5998C461.9 27.5998 456 21.5998 456 14.2998C456 6.7998 461.9 0.799805 469.3 0.799805ZM460.1 146.4V50.5998H478.6V146.4H460.1Z" fill="#23183A"/>
|
||||
<path d="M518.5 115.5C519.7 125.5 527.3 133.4 540.8 133.4C551.4 133.4 557.1 127.4 557.1 120.7C557.1 114.7 552.7 110.1 544.8 108.4L528.5 104.8C513.6 101.6 504.6 91.4996 504.6 77.8996C504.6 61.5996 519.9 47.5996 538.6 47.5996C564.9 47.5996 573 64.6996 575 73.2996L558.5 79.4996C557.7 74.4996 553.7 63.5996 538.6 63.5996C529 63.5996 522.7 69.7996 522.7 76.2996C522.7 82.0996 526.3 86.2996 533.7 87.7996L549.2 91.1996C566.5 94.9996 575.7 105.5 575.7 119.7C575.7 133.2 564.4 149.4 540.7 149.4C514.4 149.4 503.3 132.5 501.7 121.5L518.5 115.5Z" fill="#23183A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="21" height="15" viewBox="0 0 21 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.179933 0.618164C0.141568 0.674883 0.108705 0.735134 0.0817876 0.798097C0.0804492 0.812241 0.0804492 0.826481 0.0817876 0.840626C0.0577521 0.899071 0.0380694 0.959212 0.0229004 1.02056C0.0229004 1.04346 0.0229004 1.06309 0.0229004 1.08599C0.00864543 1.16262 0.000983986 1.24033 0 1.31827V13.6911C0.00326448 13.9223 0.0688148 14.1483 0.189747 14.3454L6.46122 6.35965L0.179933 0.618164Z" fill="#200C36"/>
|
||||
<path d="M10.9948 8.01518L19.5629 0.163575C19.3735 0.0587468 19.1611 0.00254309 18.9446 0H1.27847C1.06239 0.0023425 0.850153 0.0573681 0.660156 0.160304L9.2315 8.01191C9.47213 8.23213 9.78634 8.35454 10.1125 8.35515C10.4387 8.35575 10.7534 8.23451 10.9948 8.01518Z" fill="#200C36"/>
|
||||
<path d="M20.2314 1.08599C20.2314 1.06309 20.2314 1.04346 20.2314 1.02383C20.2168 0.961278 20.1971 0.90001 20.1725 0.840626C20.1739 0.826481 20.1739 0.812241 20.1725 0.798097C20.1456 0.735134 20.1128 0.674883 20.0744 0.618164L13.7637 6.35965L20.0646 14.3552C20.1855 14.1581 20.2511 13.9321 20.2543 13.7009V1.31827C20.2534 1.24033 20.2457 1.16262 20.2314 1.08599Z" fill="#200C36"/>
|
||||
<path d="M11.4412 8.49635C11.0792 8.82832 10.6058 9.01248 10.1146 9.01248C9.62343 9.01248 9.15008 8.82832 8.78804 8.49635L6.94618 6.81152L0.628906 14.8169C0.82624 14.9364 1.05249 14.9998 1.28321 15.0001H18.9493C19.18 14.9998 19.4063 14.9364 19.6036 14.8169L13.2798 6.81152L11.4412 8.49635Z" fill="#200C36"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2654 5.30175e-06C7.82844 -0.00247156 5.47025 0.862978 3.61342 2.44126C1.7566 4.01954 0.52251 6.20749 0.132324 8.61302C-0.257863 11.0185 0.221352 13.4844 1.48409 15.5687C2.74683 17.653 4.71056 19.2195 7.02335 19.9874C7.53956 20.0818 7.72212 19.7608 7.72212 19.4901C7.72212 19.2194 7.72212 18.6025 7.72212 17.7463C4.85779 18.3695 4.25344 16.3676 4.25344 16.3676C4.06342 15.7472 3.65972 15.2141 3.114 14.8631C2.1886 14.2336 3.18955 14.2336 3.18955 14.2336C3.51394 14.2794 3.82365 14.3984 4.09523 14.5816C4.36681 14.7649 4.59313 15.0075 4.75706 15.2912C4.89635 15.5437 5.08414 15.7663 5.30966 15.9461C5.53518 16.1259 5.79398 16.2594 6.07122 16.339C6.34846 16.4185 6.63868 16.4425 6.92523 16.4096C7.21177 16.3768 7.489 16.2876 7.74101 16.1473C7.78052 15.6294 8.00378 15.1427 8.37053 14.775C6.09165 14.5168 3.69946 13.6355 3.69946 9.73875C3.68381 8.7208 4.06023 7.73581 4.75077 6.98773C4.44321 6.10264 4.47916 5.13433 4.85149 4.27448C4.85149 4.27448 5.71394 3.99749 7.67176 5.32579C9.35296 4.86419 11.1275 4.86419 12.8087 5.32579C14.7665 3.99749 15.6227 4.27448 15.6227 4.27448C16.0002 5.12523 16.0451 6.08659 15.7486 6.96884C16.4391 7.71693 16.8155 8.70191 16.7999 9.71987C16.7999 13.6607 14.4014 14.5231 12.1162 14.7561C12.3612 15.0024 12.5504 15.2985 12.671 15.6243C12.7916 15.9502 12.8407 16.2982 12.815 16.6446C12.815 18.017 12.815 19.125 12.815 19.4586C12.815 19.7923 12.9975 20.0504 13.52 19.9496C15.8065 19.1614 17.7408 17.5905 18.9813 15.5143C20.2217 13.4381 20.6884 10.9903 20.299 8.6034C19.9096 6.21644 18.6891 4.04393 16.8531 2.46965C15.0171 0.895371 12.6838 0.0206469 10.2654 5.30175e-06Z" fill="#200C36"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |