mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b955e611c |
+29
-57
@@ -1,8 +1,5 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
rok8s: fairwinds/rok8s-scripts@11
|
||||
|
||||
executors:
|
||||
vm:
|
||||
machine:
|
||||
@@ -20,7 +17,6 @@ references:
|
||||
echo 'export PUSH_ALL_VERSION_TAGS=true' >> ${BASH_ENV}
|
||||
echo 'export GOPROXY=https://proxy.golang.org' >> ${BASH_ENV}
|
||||
echo 'export GO111MODULE=on' >> ${BASH_ENV}
|
||||
echo 'export GOFLAGS=-mod=mod' >> ${BASH_ENV}
|
||||
|
||||
install_k8s: &install_k8s
|
||||
run:
|
||||
@@ -62,6 +58,16 @@ references:
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
|
||||
echo "Install cert-manager successful"
|
||||
|
||||
# Test scripts
|
||||
update_coverage: &update_coverage
|
||||
run:
|
||||
name: Update Coverage
|
||||
command: |
|
||||
if [[ -z $CIRCLE_PR_NUMBER ]]; then
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
else
|
||||
echo "Skipping coverage for forked PR"
|
||||
fi
|
||||
test_binary_dashboard: &test_binary_dashboard
|
||||
run:
|
||||
name: Test Dashboard
|
||||
@@ -85,6 +91,16 @@ references:
|
||||
echo "Skipping Kubernetes tests for forked PR"
|
||||
fi
|
||||
|
||||
# Release scripts
|
||||
install_goreleaser: &install_goreleaser
|
||||
run:
|
||||
name: Install GoReleaser
|
||||
command: |
|
||||
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.131.1/goreleaser_amd64.deb
|
||||
echo "640790dcbfa864f26de4c26c2d491f293a64525c8c6641c5bbdec7136b38977e goreleaser.deb" | sha256sum -c -
|
||||
sudo dpkg -i goreleaser.deb
|
||||
rm goreleaser.deb
|
||||
|
||||
docker_build: &docker_build
|
||||
run:
|
||||
name: Docker login and build
|
||||
@@ -110,26 +126,6 @@ references:
|
||||
curl -X POST "$upload_url?name=dashboard.yaml" --data-binary "@./deploy/dashboard.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
|
||||
curl -X POST "$upload_url?name=webhook.yaml" --data-binary "@./deploy/webhook.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
|
||||
|
||||
enable_experimental_features: &enable_experimental_docker_features
|
||||
run:
|
||||
name: enable experimental features
|
||||
command: |
|
||||
set -ex
|
||||
apk --update add openssh
|
||||
ssh remote-docker \<<EOF
|
||||
sudo bash -c 'echo "{\"experimental\": true}" > /etc/docker/daemon.json'
|
||||
sudo systemctl restart docker
|
||||
EOF
|
||||
install_vault_alpine: &install_vault_alpine
|
||||
run:
|
||||
name: install hashicorp vault
|
||||
command: |
|
||||
apk --update add curl yq
|
||||
cd /tmp
|
||||
curl -LO https://releases.hashicorp.com/vault/1.9.3/vault_1.9.3_linux_amd64.zip
|
||||
unzip vault_1.9.3_linux_amd64.zip
|
||||
mv vault /usr/bin/vault
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
@@ -161,15 +157,16 @@ jobs:
|
||||
test:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
docker:
|
||||
- image: circleci/golang:1.16
|
||||
- image: circleci/golang:1.13
|
||||
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 ./... -coverprofile=coverage.txt -covermode=count
|
||||
- run: go test ./pkg/... -coverprofile=coverage.txt -covermode=count
|
||||
- run: go run main.go audit --audit-path ./deploy --set-exit-code-below-score 100 --set-exit-code-on-danger
|
||||
- *update_coverage
|
||||
- *test_binary_dashboard
|
||||
|
||||
insights:
|
||||
@@ -189,25 +186,16 @@ jobs:
|
||||
|
||||
release_binary:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
resource_class: large
|
||||
shell: /bin/bash
|
||||
docker:
|
||||
- image: goreleaser/goreleaser:v1.3.0
|
||||
- image: circleci/golang:1.13
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker:
|
||||
version: 20.10.6
|
||||
- *enable_experimental_docker_features
|
||||
- *install_vault_alpine
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/global/env
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/polaris/env
|
||||
- setup_remote_docker
|
||||
- *set_environment_variables
|
||||
- *install_goreleaser
|
||||
- run: go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
- run: git checkout -- . # FIXME: the go get makes the directory dirty
|
||||
- run: packr2
|
||||
- run: echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
|
||||
- run: goreleaser
|
||||
- *release_deploy_configs
|
||||
|
||||
@@ -220,6 +208,7 @@ jobs:
|
||||
- setup_remote_docker
|
||||
- *set_environment_variables
|
||||
- *docker_build_and_push
|
||||
|
||||
publish_docs:
|
||||
docker:
|
||||
- image: cimg/node:15.5.1
|
||||
@@ -234,34 +223,17 @@ jobs:
|
||||
npm run check-links
|
||||
npm run build
|
||||
- run:
|
||||
name: Install Tools
|
||||
name: Install AWS CLI
|
||||
command: |
|
||||
cd /tmp
|
||||
echo "Installing AWS CLI"
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
|
||||
echo "Installing Hashicorp Vault"
|
||||
curl -LO https://releases.hashicorp.com/vault/1.9.3/vault_1.9.3_linux_amd64.zip
|
||||
unzip vault_1.9.3_linux_amd64.zip
|
||||
sudo mv vault /usr/bin/vault
|
||||
sudo chmod +x /usr/bin/vault
|
||||
vault --version
|
||||
|
||||
echo "Installing yq"
|
||||
curl -LO https://github.com/mikefarah/yq/releases/download/v4.16.2/yq_linux_amd64.tar.gz
|
||||
tar -zxvf yq_linux_amd64.tar.gz
|
||||
sudo mv yq_linux_amd64 /usr/bin/yq
|
||||
sudo chmod +x /usr/bin/yq
|
||||
yq --version
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/polaris/env
|
||||
- run:
|
||||
name: Publish Docs Site to S3
|
||||
command: |
|
||||
cd ./dist
|
||||
aws s3 sync ./ s3://polaris.docs.fairwinds.com --delete
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
labels: [bug, triage]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Please fill the form below.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: What happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What did you expect to happen?
|
||||
description: What is the expected or desired behavior?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproducible
|
||||
attributes:
|
||||
label: How can we reproduce this?
|
||||
description: Please share the steps that we can take to reproduce this. Also include any relevant configuration.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: version
|
||||
description: The version of the tool that you are using.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: search
|
||||
attributes:
|
||||
label: Search
|
||||
options:
|
||||
- label: I did search for other open and closed issues before opening this.
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you agree to follow the CODE_OF_CONDUCT in this repository.
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
- type: textarea
|
||||
id: ctx
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Anything else you would like to add
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
name: Bug report
|
||||
about: Let us know about an unexpected error, a crash, or an incorrect behavior.
|
||||
|
||||
---
|
||||
### Installation Process
|
||||
<!---
|
||||
Did you install polaris on your local machine, or in a kubernetes cluster? Did you use kubectl, helm, brew, a GitHub release, or the source code? Let us know below.
|
||||
-->
|
||||
|
||||
### Polaris Version
|
||||
<!---
|
||||
If you are using polaris locally, run `polaris --version` to show the version, and paste the result between the ``` marks below.
|
||||
|
||||
If you are not running the latest version of Polaris, please try upgrading because your issue may have already been fixed.
|
||||
-->
|
||||
|
||||
```
|
||||
...
|
||||
```
|
||||
|
||||
### Expected Behavior
|
||||
<!--
|
||||
What should have happened?
|
||||
-->
|
||||
|
||||
### Actual Behavior
|
||||
<!--
|
||||
What actually happened?
|
||||
-->
|
||||
|
||||
### Steps to Reproduce
|
||||
<!--
|
||||
Please list the full steps required to reproduce the issue, for example:
|
||||
1. `polaris --audit --output-format score`
|
||||
-->
|
||||
|
||||
### Additional Context
|
||||
<!--
|
||||
Are there anything atypical about your situation that we should know?
|
||||
-->
|
||||
|
||||
### References
|
||||
<!--
|
||||
Are there any other GitHub issues (open or closed) or Pull Requests that should be linked here? For example:
|
||||
|
||||
- #6017
|
||||
|
||||
-->
|
||||
@@ -6,3 +6,5 @@ labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -3,16 +3,15 @@ if [[ -z "$INPUT_VERSION" ]]; then
|
||||
echo "Missing polaris version information"
|
||||
exit 1
|
||||
fi
|
||||
POLARIS_URL=https://github.com/FairwindsOps/polaris/releases/download/$INPUT_VERSION/polaris_linux_amd64.tar.gz
|
||||
polaris version | grep "$INPUT_VERSION" &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Polaris $INPUT_VERSION is already installed! Exiting gracefully."
|
||||
exit 0
|
||||
else
|
||||
echo "Installing polaris to path from " $POLARIS_URL
|
||||
echo "Installing polaris to path."
|
||||
fi
|
||||
TARGET_FILE="polaris.tar.gz"
|
||||
curl -LJ -o $TARGET_FILE $POLARIS_URL
|
||||
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
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/docs"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 0
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,13 +0,0 @@
|
||||
This PR fixes #
|
||||
|
||||
## Checklist
|
||||
* [ ] I have signed the CLA
|
||||
* [ ] I have updated/added any relevant documentation
|
||||
|
||||
## Description
|
||||
### What's the goal of this PR?
|
||||
|
||||
### What changes did you make?
|
||||
|
||||
### What alternative solution should we consider, if any?
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '32 1 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
exempt-issue-labels: pinned
|
||||
stale-pr-label: stale
|
||||
stale-issue-label: stale
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Setup polaris
|
||||
uses: ./.github/actions/setup-polaris
|
||||
with:
|
||||
version: 4.2.0
|
||||
version: 3.0.3
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
|
||||
@@ -22,6 +22,6 @@ jobs:
|
||||
- name: Setup polaris
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
with:
|
||||
version: 4.2.0
|
||||
version: 3.0.3
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
|
||||
+3
-22
@@ -7,40 +7,21 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
builds:
|
||||
- id: polaris
|
||||
- id: watcher
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- 386
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
archives:
|
||||
- id: polaris
|
||||
builds: ["polaris"]
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
||||
signs:
|
||||
- cmd: cosign
|
||||
args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}"]
|
||||
artifacts: checksum
|
||||
release:
|
||||
prerelease: auto
|
||||
footer: |
|
||||
You can verify the signature of the checksums.txt file using [cosign](https://github.com/sigstore/cosign).
|
||||
|
||||
```
|
||||
cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub
|
||||
```
|
||||
brews:
|
||||
- name: polaris
|
||||
tap:
|
||||
github:
|
||||
owner: FairwindsOps
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
|
||||
+1
-2
@@ -1,2 +1 @@
|
||||
## DO NOT EDIT - Managed by Terraform
|
||||
* @rbren @makoscafee
|
||||
* @rbren @makoscafee @jordandoig @baderbuddy @shaswa
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource@fairwinds.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.16 AS build-env
|
||||
FROM golang:1.13 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.14
|
||||
FROM alpine:3.13
|
||||
WORKDIR /usr/local/bin
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<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=5.0.0&color=239922">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.2.0&color=239922">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
|
||||
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
|
||||
</a>
|
||||
<a href="https://circleci.com/gh/FairwindsOps/polaris">
|
||||
<a href="https://circleci.com/gh/FairwindsOps/polaris.svg">
|
||||
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
|
||||
</a>
|
||||
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
|
||||
@@ -29,38 +29,41 @@ Polaris can be run in three different modes:
|
||||
<img src="https://polaris.docs.fairwinds.com/img/architecture.svg" alt="Polaris Architecture" width="550"/>
|
||||
</p>
|
||||
|
||||
**Want to learn more?** Reach out on [the Slack channel](https://fairwindscommunity.slack.com/messages/polaris) ([request invite](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)), send an email to `opensource@fairwinds.com`, or join us for [office hours on Zoom](https://fairwindscommunity.slack.com/messages/office-hours)
|
||||
|
||||
|
||||
## Documentation
|
||||
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
|
||||
|
||||
<!-- Begin boilerplate -->
|
||||
## Join the Fairwinds Open Source Community
|
||||
## Integration with Fairwinds Insights
|
||||
<p align="center">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/FW_Insights_Polaris.svg" alt="Fairwinds Insights" width="550"/>
|
||||
</p>
|
||||
|
||||
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap,
|
||||
and network with fellow Kubernetes users.
|
||||
[Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)
|
||||
or
|
||||
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
|
||||
[Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
is a platform for auditing Kubernetes clusters and enforcing policy. If you'd like to:
|
||||
* manage Polaris across a fleet of clusters
|
||||
* track findings over time
|
||||
* send results to services like Slack and Datadog
|
||||
* add additional checks from tools like
|
||||
[Trivy](https://github.com/aquasecurity/trivy),
|
||||
[Goldilocks](https://github.com/FairwindsOps/goldilocks/), and
|
||||
[OPA](https://www.openpolicyagent.org)
|
||||
|
||||
<a href="https://www.fairwinds.com/t-shirt-offer?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris-tshirt">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png" alt="Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt!" />
|
||||
</a>
|
||||
you can sign up for a [free account here](https://insights.fairwinds.com?source=polaris).
|
||||
|
||||
## Other Projects from Fairwinds
|
||||
## Contributing
|
||||
PRs welcome! Check out the [Contributing Guidelines](https://polaris.docs.fairwinds.com/contributing) and [Code of Conduct](https://polaris.docs.fairwinds.com/code-of-conduct) for more information.
|
||||
|
||||
Enjoying Polaris? Check out some of our other projects:
|
||||
* [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage
|
||||
* [Pluto](https://github.com/FairwindsOps/Pluto) - Detect Kubernetes resources that have been deprecated or removed in future versions
|
||||
* [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available
|
||||
* [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters
|
||||
## Further Information
|
||||
A history of changes to this project can be viewed in the [Changelog](https://polaris.docs.fairwinds.com/changelog)
|
||||
|
||||
Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris)
|
||||
## Fairwinds Insights
|
||||
If you're interested in running Polaris in multiple clusters,
|
||||
tracking the results over time, integrating with Slack, Datadog, and Jira,
|
||||
or unlocking other functionality, check out
|
||||
[Fairwinds Insights](https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris),
|
||||
a platform for auditing and enforcing policy in Kubernetes clusters.
|
||||
If you'd like to learn more about Polaris, or if you'd like to speak with
|
||||
a Kubernetes expert, you can contact `info@fairwinds.com` or [visit our website](https://fairwinds.com)
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
|
||||
</p>
|
||||
|
||||
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_Polaris_Ad.png" alt="Fairwinds Insights" />
|
||||
</a>
|
||||
|
||||
@@ -14,13 +14,12 @@ schema:
|
||||
properties:
|
||||
add:
|
||||
type: array
|
||||
allOf:
|
||||
- not:
|
||||
contains:
|
||||
pattern: '^(?i)ALL$'
|
||||
- not:
|
||||
contains:
|
||||
pattern: '^(?i)SYS_ADMIN$'
|
||||
- not:
|
||||
contains:
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
not:
|
||||
contains:
|
||||
const: ALL
|
||||
not:
|
||||
contains:
|
||||
const: SYS_ADMIN
|
||||
not:
|
||||
contains:
|
||||
const: NET_ADMIN
|
||||
|
||||
@@ -22,35 +22,36 @@ schema:
|
||||
type: array
|
||||
oneOf:
|
||||
- contains:
|
||||
pattern: '^(?i)ALL$'
|
||||
const: ALL
|
||||
- allOf:
|
||||
- contains:
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
const: NET_ADMIN
|
||||
- contains:
|
||||
pattern: '^(?i)CHOWN$'
|
||||
const: CHOWN
|
||||
- contains:
|
||||
pattern: '^(?i)DAC_OVERRIDE$'
|
||||
const: DAC_OVERRIDE
|
||||
- contains:
|
||||
pattern: '^(?i)FSETID$'
|
||||
const: FSETID
|
||||
- contains:
|
||||
pattern: '^(?i)FOWNER$'
|
||||
const: FOWNER
|
||||
- contains:
|
||||
pattern: '^(?i)MKNOD$'
|
||||
const: MKNOD
|
||||
- contains:
|
||||
pattern: '^(?i)NET_RAW$'
|
||||
const: NET_RAW
|
||||
- contains:
|
||||
pattern: '^(?i)SETGID$'
|
||||
const: SETGID
|
||||
- contains:
|
||||
pattern: '^(?i)SETUID$'
|
||||
const: SETUID
|
||||
- contains:
|
||||
pattern: '^(?i)SETFCAP$'
|
||||
const: SETFCAP
|
||||
- contains:
|
||||
pattern: '^(?i)SETPCAP$'
|
||||
const: SETPCAP
|
||||
- contains:
|
||||
pattern: '^(?i)NET_BIND_SERVICE$'
|
||||
const: NET_BIND_SERVICE
|
||||
- contains:
|
||||
pattern: '^(?i)SYS_CHROOT$'
|
||||
const: SYS_CHROOT
|
||||
- contains:
|
||||
pattern: '^(?i)KILL$'
|
||||
const: KILL
|
||||
- contains:
|
||||
pattern: '^(?i)AUDIT_WRITE$'
|
||||
const: AUDIT_WRITE
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
successMessage: Label app.kubernetes.io/name matches metadata.name
|
||||
failureMessage: Label app.kubernetes.io/name must match metadata.name
|
||||
target: Controller
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["labels"]
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
required: ["app.kubernetes.io/name"]
|
||||
properties:
|
||||
app.kubernetes.io/name:
|
||||
const: "{{ .metadata.name }}"
|
||||
@@ -1,39 +0,0 @@
|
||||
successMessage: A PodDisruptionBudget is attached
|
||||
failureMessage: Should have a PodDisruptionBudget
|
||||
category: Reliability
|
||||
target: Controller
|
||||
controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
minProperties: 1
|
||||
additionalSchemaStrings:
|
||||
policy/PodDisruptionBudget: |
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: ["selector"]
|
||||
properties:
|
||||
selector:
|
||||
type: object
|
||||
required: ["matchLabels"]
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
anyOf:
|
||||
{{ range $key, $value := .metadata.labels }}
|
||||
- properties:
|
||||
"{{ $key }}":
|
||||
type: string
|
||||
const: {{ $value }}
|
||||
required: ["{{ $key }}"]
|
||||
{{ end }}
|
||||
@@ -2,42 +2,15 @@ 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
|
||||
definitions:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- readOnlyRootFilesystem
|
||||
properties:
|
||||
readOnlyRootFilesystem:
|
||||
const: true
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
required:
|
||||
- readOnlyRootFilesystem
|
||||
properties:
|
||||
readOnlyRootFilesystem:
|
||||
const: true
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
@@ -1,22 +0,0 @@
|
||||
successMessage: Voluntary evictions are possible
|
||||
failureMessage: Voluntary evictions are not possible
|
||||
category: Reliability
|
||||
target: policy/PodDisruptionBudget
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
minAvailable:
|
||||
not:
|
||||
const: '100%'
|
||||
maxUnavailable:
|
||||
allOf:
|
||||
- not:
|
||||
const: 0
|
||||
- not:
|
||||
const: '0%'
|
||||
@@ -2,42 +2,15 @@ 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
|
||||
definitions:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- allowPrivilegeEscalation
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
const: false
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
required:
|
||||
- allowPrivilegeEscalation
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
const: false
|
||||
type: object
|
||||
anyOf:
|
||||
- required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
@@ -2,23 +2,12 @@ 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
|
||||
definitions:
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
properties:
|
||||
privileged:
|
||||
not:
|
||||
const: true
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
@@ -1,7 +1,7 @@
|
||||
successMessage: Ingress has TLS configured
|
||||
failureMessage: Ingress does not have TLS configured
|
||||
category: Security
|
||||
target: networking.k8s.io/Ingress
|
||||
target: Ingress
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
|
||||
+13
-60
@@ -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"
|
||||
@@ -39,8 +39,6 @@ var auditOutputFile string
|
||||
var auditOutputFormat string
|
||||
var resourceToAudit string
|
||||
var useColor bool
|
||||
var helmChart string
|
||||
var helmValues string
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(auditCmd)
|
||||
@@ -54,8 +52,6 @@ func init() {
|
||||
auditCmd.PersistentFlags().BoolVar(&useColor, "color", true, "Whether to use color in pretty format.")
|
||||
auditCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
|
||||
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
|
||||
auditCmd.PersistentFlags().StringVar(&helmChart, "helm-chart", "", "Will fill out Helm template")
|
||||
auditCmd.PersistentFlags().StringVar(&helmValues, "helm-values", "", "Optional flag to add helm values")
|
||||
}
|
||||
|
||||
var auditCmd = &cobra.Command{
|
||||
@@ -66,28 +62,8 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
k, err := kube.CreateResourceProvider(context.TODO(), auditPath, resourceToAudit, config)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
auditData, err := validator.RunAudit(config, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests)
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat, useColor)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -101,50 +77,26 @@ var auditCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// ProcessHelmTemplates turns helm into yaml to be processed by Polaris or the other tools.
|
||||
func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
|
||||
cmd := exec.Command("helm", "dependency", "update", helmChart)
|
||||
output, err := cmd.CombinedOutput()
|
||||
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string, useColor bool) validator.AuditData {
|
||||
// Create a kubernetes client resource provider
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, workload)
|
||||
if err != nil {
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params := []string{
|
||||
"template", helmChart,
|
||||
helmChart,
|
||||
"--output-dir",
|
||||
dir,
|
||||
}
|
||||
if helmValues != "" {
|
||||
params = append(params, "--values", helmValues)
|
||||
}
|
||||
|
||||
cmd = exec.Command("helm", params...)
|
||||
output, err = cmd.CombinedOutput()
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(c, k, onlyShowFailedTests)
|
||||
|
||||
if err != nil {
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFormat string, useColor bool, onlyShowFailedTests bool) {
|
||||
if onlyShowFailedTests {
|
||||
auditData = auditData.RemoveSuccessfulResults()
|
||||
}
|
||||
var outputBytes []byte
|
||||
var err error
|
||||
if outputFormat == "score" {
|
||||
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
|
||||
} else if outputFormat == "yaml" {
|
||||
var jsonBytes []byte
|
||||
jsonBytes, err = json.Marshal(auditData)
|
||||
jsonBytes, err := json.Marshal(auditData)
|
||||
if err == nil {
|
||||
outputBytes, err = yaml.JSONToYAML(jsonBytes)
|
||||
}
|
||||
@@ -203,4 +155,5 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
|
||||
}
|
||||
}
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
+6
-7
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var configPath string
|
||||
var disallowExemptions, disallowConfigExemptions, disallowAnnotationExemptions bool
|
||||
var disallowExemptions bool
|
||||
var logLevel string
|
||||
var auditPath string
|
||||
var displayName string
|
||||
@@ -37,9 +37,7 @@ var (
|
||||
func init() {
|
||||
// Flags
|
||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Location of Polaris configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any configured exemption.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowConfigExemptions, "disallow-config-exemptions", "", false, "Disallow exemptions set within the configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowAnnotationExemptions, "disallow-annotation-exemptions", "", false, "Disallow any exemption defined as a controller annotation.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any exemptions from configuration file.")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level.")
|
||||
flag.Parse()
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
@@ -65,9 +63,10 @@ var rootCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config.DisallowExemptions = disallowExemptions
|
||||
config.DisallowConfigExemptions = disallowConfigExemptions
|
||||
config.DisallowAnnotationExemptions = disallowAnnotationExemptions
|
||||
if disallowExemptions {
|
||||
config.DisallowExemptions = true
|
||||
}
|
||||
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Error("You must specify a sub-command.")
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
coverage:
|
||||
range: 50...80
|
||||
+2
-12
@@ -31,14 +31,6 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -102,7 +94,7 @@ metadata:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
spec:
|
||||
replicas: 2
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
@@ -117,9 +109,7 @@ spec:
|
||||
- command:
|
||||
- polaris
|
||||
- dashboard
|
||||
- --port
|
||||
- "8080"
|
||||
image: 'quay.io/fairwinds/polaris:5.0'
|
||||
image: 'quay.io/fairwinds/polaris:3.1'
|
||||
imagePullPolicy: 'Always'
|
||||
name: dashboard
|
||||
ports:
|
||||
|
||||
+2
-10
@@ -31,14 +31,6 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -101,7 +93,7 @@ metadata:
|
||||
app: polaris
|
||||
component: webhook
|
||||
spec:
|
||||
replicas: 2
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
@@ -117,7 +109,7 @@ spec:
|
||||
command:
|
||||
- polaris
|
||||
- webhook
|
||||
image: 'quay.io/fairwinds/polaris:5.0'
|
||||
image: 'quay.io/fairwinds/polaris:3.1'
|
||||
imagePullPolicy: 'Always'
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
|
||||
@@ -42,7 +42,7 @@ const baseConfig = {
|
||||
],
|
||||
themeConfig: {
|
||||
docsRepo: "",
|
||||
docsDir: 'docs',
|
||||
docsDir: 'docs-md',
|
||||
editLinks: true,
|
||||
editLinkText: "Help us improve this page",
|
||||
logo: '/img/fairwinds-logo.svg',
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB |
@@ -23,13 +23,6 @@
|
||||
color: $successColor;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.2rem solid $warningColor;
|
||||
}
|
||||
blockquote p {
|
||||
color: $warningColor;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom),
|
||||
.page-nav,
|
||||
.page-edit,
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation: Polaris can be run as an admission controller that acts as a validating webhook."
|
||||
---
|
||||
# Admission Controller
|
||||
> Want to manage the Admission Controller across multiple clusters? Check out
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
@@ -33,7 +28,7 @@ kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/downloa
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace \
|
||||
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris \
|
||||
--set webhook.enable=true --set dashboard.enable=false
|
||||
```
|
||||
|
||||
|
||||
@@ -1,61 +1,6 @@
|
||||
---
|
||||
sidebarDepth: 0
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Changelog"
|
||||
|
||||
---
|
||||
## 5.0.0
|
||||
* Renamed `multipleReplicasForDeployment` to `deploymentMissingReplicas`
|
||||
* Changed `RunAsRootAllowed` and `hostNetworkSet` default severity to `danger`
|
||||
* Changed `deploymentMissingReplicas` default severity to `warning`
|
||||
|
||||
## 4.2.0
|
||||
* New flags `--disallow-(config|annotation)-exemptions`
|
||||
* Kubernetes dependency updates
|
||||
* Documentation updates
|
||||
## 4.1.0
|
||||
* Handle case-insentitivity in capabilities checks
|
||||
* Change test for PDB disruptions to better handle IaC
|
||||
|
||||
## 4.0.9
|
||||
* Update Alpine base image
|
||||
|
||||
## 4.0.8
|
||||
* Fix support for namespace checks
|
||||
|
||||
## 4.0.7
|
||||
* Fix goreleaser format
|
||||
|
||||
## 4.0.6
|
||||
* Change goreleaser format
|
||||
* Fix `--helm-values` flag
|
||||
|
||||
## 4.0.5
|
||||
* Bugfix for repeated objects on the dashboard
|
||||
|
||||
## 4.0.4
|
||||
* Bugfix for validating webhook and non-pod checks
|
||||
|
||||
## 4.0.3
|
||||
* Fixed bad interaction between `--set-exit-score-below` and `--only-show-failed-tests`
|
||||
* Dependency updates
|
||||
* Support for Helm chart scanning
|
||||
|
||||
## 4.0.2
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.1
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.0
|
||||
* Add support for arbitrary resources, like Ingress or PodDisruptionBudget
|
||||
* Add support check templating (see docs)
|
||||
* Add support for multi-resource checks (see docs)
|
||||
|
||||
### Breaking Changes
|
||||
* In custom checks, `jsonSchema` is now `schemaString`
|
||||
* Check `pdbDisruptionsAllowedGreaterThanZero` is now called `pdbDisruptionsIsZero`
|
||||
|
||||
## 3.2.0
|
||||
* Add `--format=pretty` option for CLI output
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ensure that CPU and memory settings are configured, so that Kubernetes can schedule your workload effectively"
|
||||
---
|
||||
# Efficiency
|
||||
|
||||
These checks ensure that CPU and memory settings are configured, so that
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Make sure your Kubernetes workloads are always available, and are running the correct image."
|
||||
---
|
||||
# Reliability
|
||||
|
||||
These checks help to make sure your workloads are always available,
|
||||
@@ -15,8 +10,7 @@ key | default | description
|
||||
`tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
|
||||
`deploymentMissingReplicas` | `warning` | Fails when there is only one replica for a deployment.
|
||||
`missingPodDisruptionBudget` | `ignore`
|
||||
`multipleReplicasForDeployment` | `ignore` | Fails when there is only one replica for a deployment.
|
||||
|
||||
## Background
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Security Checks Documentation"
|
||||
---
|
||||
# Security
|
||||
|
||||
These checks are related to security concerns. Workloads that fail these
|
||||
|
||||
+5
-13
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Learn your CLI options"
|
||||
---
|
||||
#### CLI Options
|
||||
|
||||
```
|
||||
@@ -19,12 +14,10 @@ webhook
|
||||
Runs the webhook webserver
|
||||
|
||||
# global flags
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
--disallow-exemptions Disallow any exemptions from configuration file.
|
||||
--disallow-config-exemptions Disallow exemptions set within the configuration file.
|
||||
--disallow-annotation-exemptions Disallow any exemption defined as a controller annotation.
|
||||
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string Logrus log level. (default "info")
|
||||
-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.
|
||||
@@ -40,8 +33,6 @@ webhook
|
||||
--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.
|
||||
@@ -50,6 +41,7 @@ webhook
|
||||
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
|
||||
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
|
||||
|
||||
|
||||
# webhook flags
|
||||
--disable-webhook-config-installer disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.
|
||||
-h, --help help for webhook
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Code of Conduct"
|
||||
---
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Guidelines"
|
||||
---
|
||||
# Contributing
|
||||
|
||||
Issues, whether bugs, tasks, or feature requests are essential for keeping Polaris great. We believe it should be as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can keep on top of things.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation on setting checks by severity "
|
||||
---
|
||||
# Check Settings
|
||||
Each check can be assigned a `severity`. Only checks with a severity of `danger` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `danger` will result in a change being rejected.
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Learn about Polaris defaults and how to customize configurations. "
|
||||
---
|
||||
# Configuration
|
||||
|
||||
The default Polaris configuration can be [seen here](https://github.com/FairwindsOps/polaris/blob/master/examples/config.yaml).
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation: Create your own checks, you can use JSON Schema"
|
||||
---
|
||||
# Custom Checks
|
||||
|
||||
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/).
|
||||
This is how built-in Polaris checks are defined as well - you can see all the built-in checks
|
||||
in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/checks) for examples.
|
||||
|
||||
If you write a check that could be useful for others, feel free to open a PR to add it in!
|
||||
|
||||
## Basic Example
|
||||
For example, to disallow images from quay.io:
|
||||
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/). For example,
|
||||
to disallow images from quay.io:
|
||||
|
||||
```yaml
|
||||
checks:
|
||||
imageRegistry: warning
|
||||
|
||||
customChecks:
|
||||
imageRegistry:
|
||||
successMessage: Image comes from allowed registries
|
||||
failureMessage: Image should not be from disallowed registry
|
||||
category: Security
|
||||
target: Container
|
||||
category: Images
|
||||
target: Container # target can be "Container" or "Pod"
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
@@ -34,181 +21,6 @@ customChecks:
|
||||
pattern: ^quay.io
|
||||
```
|
||||
|
||||
## Available Options
|
||||
All custom checks should go under the `customChecks` field in your Polaris config, keyed by the
|
||||
check ID. Note that you'll also have to set its severity in the `checks` section of your Polaris config.
|
||||
|
||||
* `successMessage` - the message to show when the check succeeds
|
||||
* `failureMessage` - the message to show when the check fails
|
||||
* `category` - one of `Security`, `Efficiency`, or `Reliability`
|
||||
* `target` - specifies the type of resource to check. This can be:
|
||||
* a group and kind, e.g. `apps/Deployment` or `networking.k8s.io/Ingress`
|
||||
* `Controller`, to check _any_ resource that 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:
|
||||
@@ -220,3 +32,8 @@ customChecks:
|
||||
}
|
||||
```
|
||||
|
||||
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
|
||||
strings like `1000m` and `1G`. You can see an example in [the extended config](https://github.com/FairwindsOps/polaris/tree/master/examples/config-full.yaml)
|
||||
|
||||
There are additional examples in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/checks).
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
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
|
||||
|
||||
+1
-6
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ways to run Polaris | The Polaris dashboard can be installed on a cluster using kubectl or Helm"
|
||||
---
|
||||
# Dashboard
|
||||
|
||||
> Want to see Polaris results for all your clusters in a single dashboard? Check out
|
||||
@@ -24,7 +19,7 @@ kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
### Helm
|
||||
```bash
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace
|
||||
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
|
||||
```
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation | Polaris can be used on the command line to audit local Kubernetes manifests stored in YAML files."
|
||||
---
|
||||
# Infrastructure as Code
|
||||
> Want to see results for all your IaC repos in one place? Check out
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
@@ -60,14 +55,6 @@ 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
|
||||
|
||||
@@ -88,9 +75,9 @@ The version number of the release tag.
|
||||
##### Example usage
|
||||
|
||||
```yaml
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
uses: fairwindsops/polaris@master
|
||||
with:
|
||||
version: 5.0.0
|
||||
version: "3.0.3"
|
||||
```
|
||||
|
||||
Example inside a job:
|
||||
@@ -99,9 +86,9 @@ Example inside a job:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup polaris
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
uses: fairwindsops/polaris@master
|
||||
with:
|
||||
version: 5.0.0
|
||||
version: 3.0.3
|
||||
|
||||
- name: Use command
|
||||
run: polaris version
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation"
|
||||
---
|
||||
Generated
+1843
-16605
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -26,8 +26,7 @@
|
||||
"scripts": {
|
||||
"build": "npm run build:readme && npm run build:docs",
|
||||
"build:docs": "vuepress build -d ../dist/",
|
||||
"build:metadata": "cat main-metadata.md > README.md || true",
|
||||
"build:readme": "npm run build:metadata && cat ../README.md | grep -v 'ocumentation' | sed \"s/https:\\/\\/\\w\\+.docs.fairwinds.com//g\" >> README.md",
|
||||
"build:readme": "cat ../README.md | grep -v 'ocumentation' | sed \"s/https:\\/\\/\\w\\+.docs.fairwinds.com//g\" > README.md",
|
||||
"check-links": "vuepress check-md",
|
||||
"serve": "npm run build:readme && vuepress dev --port 3003",
|
||||
"vuepress": "vuepress"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
checks:
|
||||
# reliability
|
||||
deploymentMissingReplicas: warning
|
||||
multipleReplicasForDeployment: warning
|
||||
priorityClassNotSet: warning
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
@@ -16,11 +16,11 @@ checks:
|
||||
hostPIDSet: danger
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
runAsRootAllowed: danger
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: danger
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
# custom
|
||||
resourceLimits: warning
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
checks:
|
||||
# reliability
|
||||
deploymentMissingReplicas: warning
|
||||
multipleReplicasForDeployment: ignore
|
||||
priorityClassNotSet: ignore
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
metadataAndNameMismatched: ignore
|
||||
pdbDisruptionsIsZero: warning
|
||||
missingPodDisruptionBudget: ignore
|
||||
|
||||
# efficiency
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
@@ -20,11 +16,11 @@ checks:
|
||||
hostPIDSet: danger
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
runAsRootAllowed: danger
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: danger
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
tlsSettingsMissing: warning
|
||||
|
||||
|
||||
@@ -1,25 +1,38 @@
|
||||
module github.com/fairwindsops/polaris
|
||||
|
||||
go 1.15
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.13.0
|
||||
cloud.google.com/go v0.74.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.15 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.10 // indirect
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/gobuffalo/packr/v2 v2.8.1
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.1.3 // indirect
|
||||
github.com/googleapis/gnostic v0.5.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.1 // indirect
|
||||
github.com/prometheus/client_golang v1.9.0 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/sirupsen/logrus v1.8.0
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/thoas/go-funk v0.9.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
sigs.k8s.io/controller-runtime v0.10.1
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
go.uber.org/zap v1.16.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
|
||||
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 // indirect
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
k8s.io/api v0.20.4
|
||||
k8s.io/apimachinery v0.20.4
|
||||
k8s.io/client-go v0.20.4
|
||||
k8s.io/component-base v0.20.1 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.7.0
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
cmd "github.com/fairwindsops/polaris/cmd/polaris"
|
||||
"github.com/fairwindsops/polaris/cmd/polaris"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "5.0.0"
|
||||
Version = "3.2.0"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// BuiltInChecks contains the checks that come pre-installed w/ Polaris
|
||||
BuiltInChecks = map[string]SchemaCheck{}
|
||||
schemaBox = (*packr.Box)(nil)
|
||||
// We explicitly set the order to avoid thrash in the
|
||||
// tests as we migrate toward JSON schema
|
||||
checkOrder = []string{
|
||||
// Controller Checks
|
||||
"deploymentMissingReplicas",
|
||||
// Pod checks
|
||||
"hostIPCSet",
|
||||
"hostPIDSet",
|
||||
"hostNetworkSet",
|
||||
// Container checks
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
"cpuLimitsMissing",
|
||||
"cpuRequestsMissing",
|
||||
"readinessProbeMissing",
|
||||
"livenessProbeMissing",
|
||||
"pullPolicyNotAlways",
|
||||
"tagNotSpecified",
|
||||
"hostPortSet",
|
||||
"runAsRootAllowed",
|
||||
"runAsPrivileged",
|
||||
"notReadOnlyRootFilesystem",
|
||||
"privilegeEscalationAllowed",
|
||||
"dangerousCapabilities",
|
||||
"insecureCapabilities",
|
||||
"priorityClassNotSet",
|
||||
// Other checks
|
||||
"tlsSettingsMissing",
|
||||
"pdbDisruptionsIsZero",
|
||||
"metadataAndNameMismatched",
|
||||
"missingPodDisruptionBudget",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
schemaBox = packr.New("Schemas", "../../checks")
|
||||
for _, checkID := range checkOrder {
|
||||
contents, err := schemaBox.Find(checkID + ".yaml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
check, err := ParseCheck(checkID, contents)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while parsing check %s", checkID)
|
||||
panic(err)
|
||||
}
|
||||
BuiltInChecks[checkID] = check
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,11 @@ import (
|
||||
|
||||
// Configuration contains all of the config for the validation checks.
|
||||
type Configuration struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Checks map[string]Severity `json:"checks"`
|
||||
CustomChecks map[string]SchemaCheck `json:"customChecks"`
|
||||
Exemptions []Exemption `json:"exemptions"`
|
||||
DisallowExemptions bool `json:"disallowExemptions"`
|
||||
DisallowConfigExemptions bool `json:"disallowConfigExemptions"`
|
||||
DisallowAnnotationExemptions bool `json:"disallowAnnotationExemptions"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Checks map[string]Severity `json:"checks"`
|
||||
CustomChecks map[string]SchemaCheck `json:"customChecks"`
|
||||
Exemptions []Exemption `json:"exemptions"`
|
||||
DisallowExemptions bool `json:"disallowExemptions"`
|
||||
}
|
||||
|
||||
// Exemption represents an exemption to normal rules
|
||||
|
||||
@@ -16,7 +16,6 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -143,9 +142,7 @@ func TestConfigNoServerError(t *testing.T) {
|
||||
|
||||
func TestConfigWithCustomChecks(t *testing.T) {
|
||||
valid := map[string]interface{}{
|
||||
"securityContext": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
"securityContext": map[string]interface{}{},
|
||||
}
|
||||
invalid := map[string]interface{}{
|
||||
"notSecurityContext": map[string]interface{}{},
|
||||
@@ -154,23 +151,20 @@ func TestConfigWithCustomChecks(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confCustomChecks))
|
||||
assert.NoError(t, err, "Expected no error when parsing YAML config")
|
||||
assert.Equal(t, 1, len(parsedConf.CustomChecks))
|
||||
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]interface{}{})
|
||||
isValid, _, err := check.CheckObject(valid)
|
||||
isValid, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
|
||||
parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
|
||||
assert.NoError(t, err, "Expected no error when parsing YAML config")
|
||||
assert.Equal(t, 1, len(parsedConf.CustomChecks))
|
||||
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
assert.NoError(t, err)
|
||||
if !assert.Equal(t, true, isValid) {
|
||||
fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
|
||||
}
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
}
|
||||
|
||||
@@ -2,20 +2,18 @@ package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// IsActionable determines whether a check is actionable given the current configuration
|
||||
func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, containerName string) bool {
|
||||
func (conf Configuration) IsActionable(ruleID, namespace, controllerName, containerName string) bool {
|
||||
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
|
||||
return false
|
||||
}
|
||||
if conf.DisallowExemptions || conf.DisallowConfigExemptions {
|
||||
if conf.DisallowExemptions {
|
||||
return true
|
||||
}
|
||||
for _, exemption := range conf.Exemptions {
|
||||
if exemption.Namespace != "" && exemption.Namespace != objMeta.GetNamespace() {
|
||||
if exemption.Namespace != "" && exemption.Namespace != namespace {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -29,7 +27,7 @@ func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, con
|
||||
}
|
||||
|
||||
if len(exemption.Rules) == 0 || checkIfRuleMatches {
|
||||
if !isExemptionCheckMatched(exemption.ControllerNames, objMeta.GetName()) {
|
||||
if !isExemptionCheckMatched(exemption.ControllerNames, controllerName) {
|
||||
continue
|
||||
}
|
||||
if isExemptionCheckMatched(exemption.ContainerNames, containerName) {
|
||||
|
||||
@@ -18,47 +18,44 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
var confContainerTest = `
|
||||
checks:
|
||||
deploymentMissingReplicas: warning
|
||||
multipleReplicasForDeployment: warning
|
||||
priorityClassNotSet: warning
|
||||
pullPolicyNotAlways: warning
|
||||
exemptions:
|
||||
- namespace: prometheus
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- multipleReplicasForDeployment
|
||||
- controllerNames:
|
||||
- controller2
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- multipleReplicasForDeployment
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- controller3
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- multipleReplicasForDeployment
|
||||
- containerNames:
|
||||
- container41
|
||||
- container42
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- multipleReplicasForDeployment
|
||||
- namespace: kube-system
|
||||
containerNames:
|
||||
- container51
|
||||
- container52
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- multipleReplicasForDeployment
|
||||
- controllerNames:
|
||||
- controller6
|
||||
containerNames:
|
||||
- container61
|
||||
- container62
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- multipleReplicasForDeployment
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
- controller7
|
||||
@@ -66,42 +63,31 @@ exemptions:
|
||||
- container71
|
||||
- container72
|
||||
rules:
|
||||
- deploymentMissingReplicas
|
||||
- multipleReplicasForDeployment
|
||||
- priorityClassNotSet
|
||||
- namespace: polaris
|
||||
`
|
||||
|
||||
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 TestNamespaceExemptionForSpecifiedRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", ""), "")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller1"), "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", ""), "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller1"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("prometheus", "controller1"), "")
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "prometheus", "controller1", "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -109,19 +95,19 @@ func TestNamespaceExemptionForAllRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", ""), "")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", "controller1"), "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", ""), "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("polaris", "controller1"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("polaris", "controller1"), "")
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "polaris", "controller1", "")
|
||||
assert.False(t, actionable)
|
||||
}
|
||||
|
||||
@@ -129,28 +115,28 @@ func TestControllerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller2"), "")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller2"), "container21")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller2"), "container21")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("prometheus", "controller2"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller3"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller3", "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller3"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller3"), "container31")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "container31")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller4"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -158,22 +144,22 @@ func TestOnlyContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container41")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container42")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container42")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller4"), "container41")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller4", "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container41")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller4"), "container41")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -181,25 +167,25 @@ func TestNamespaceAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container51")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container51")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", ""), "container51")
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "", "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller5"), "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "container51")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller5"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("insights-agent", ""), "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "", "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller5"), "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller5", "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -207,25 +193,25 @@ func TestControllerAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller6"), "container61")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller6", "container61")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("", "controller6"), "container61")
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", "", "controller6", "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container61")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller7"), "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container61")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -233,33 +219,33 @@ func TestContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", ""), "container71")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", ""), "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("", "controller7"), "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container71")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("insights-agent", "controller7"), "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "controller7", "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller6"), "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("deploymentMissingReplicas", createMeta("kube-system", "controller7"), "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", "controller7"), "container71")
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "controller7", "container71")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("kube-system", "controller8"), "container71")
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "kube-system", "controller8", "container71")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
+29
-158
@@ -1,86 +1,45 @@
|
||||
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"
|
||||
// TargetIngress points to the ingress spec
|
||||
TargetIngress TargetKind = "Ingress"
|
||||
)
|
||||
|
||||
// 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" 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:"-"`
|
||||
ID string `yaml:"id"`
|
||||
Category string `yaml:"category"`
|
||||
SuccessMessage string `yaml:"successMessage"`
|
||||
FailureMessage string `yaml:"failureMessage"`
|
||||
Controllers includeExcludeList `yaml:"controllers"`
|
||||
Containers includeExcludeList `yaml:"containers"`
|
||||
Target TargetKind `yaml:"target"`
|
||||
SchemaTarget TargetKind `yaml:"schemaTarget"`
|
||||
Schema jsonschema.RootSchema `yaml:"schema"`
|
||||
JSONSchema string `yaml:"jsonSchema"`
|
||||
}
|
||||
|
||||
type resourceMinimum string
|
||||
type resourceMaximum string
|
||||
|
||||
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)
|
||||
@@ -160,136 +119,48 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
|
||||
// Initialize sets up the schema
|
||||
func (check *SchemaCheck) Initialize(id string) error {
|
||||
check.ID = id
|
||||
if check.SchemaString == "" {
|
||||
jsonBytes, err := json.Marshal(check.Schema)
|
||||
if err != nil {
|
||||
if check.JSONSchema != "" {
|
||||
if err := json.Unmarshal([]byte(check.JSONSchema), &check.Schema); err != nil {
|
||||
return err
|
||||
}
|
||||
check.SchemaString = string(jsonBytes)
|
||||
}
|
||||
if check.AdditionalSchemaStrings == nil {
|
||||
check.AdditionalSchemaStrings = make(map[string]string)
|
||||
}
|
||||
for kind, schema := range check.AdditionalSchemas {
|
||||
jsonBytes, err := json.Marshal(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
check.AdditionalSchemaStrings[kind] = string(jsonBytes)
|
||||
}
|
||||
check.Schema = map[string]interface{}{}
|
||||
check.AdditionalSchemas = map[string]map[string]interface{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TemplateForResource fills out a check's templated fields given a particular resource
|
||||
func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, error) {
|
||||
newCheck := check // Make a copy of the check, since we're going to modify the schema
|
||||
|
||||
templateStrings := map[string]string{
|
||||
"": newCheck.SchemaString,
|
||||
}
|
||||
for kind, schema := range newCheck.AdditionalSchemaStrings {
|
||||
templateStrings[kind] = schema
|
||||
}
|
||||
newCheck.SchemaString = ""
|
||||
newCheck.AdditionalSchemaStrings = map[string]string{}
|
||||
|
||||
for kind, tmplString := range templateStrings {
|
||||
tmpl := template.New(newCheck.ID)
|
||||
tmpl, err := tmpl.Parse(tmplString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := bytes.Buffer{}
|
||||
err = tmpl.Execute(&w, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if kind == "" {
|
||||
newCheck.SchemaString = w.String()
|
||||
} else {
|
||||
newCheck.AdditionalSchemaStrings[kind] = w.String()
|
||||
}
|
||||
}
|
||||
|
||||
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
|
||||
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
|
||||
val := jsonschema.RootSchema{}
|
||||
err := unmarshalYAMLOrJSON([]byte(schemaStr), &val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newCheck.AdditionalValidators[kind] = val
|
||||
}
|
||||
err := unmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newCheck, err
|
||||
}
|
||||
|
||||
// CheckPod checks a pod spec against the schema
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
|
||||
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
|
||||
return check.CheckObject(pod)
|
||||
}
|
||||
|
||||
// CheckController checks a controler's spec against the schema
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, error) {
|
||||
errs, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errs) == 0, err
|
||||
}
|
||||
|
||||
// CheckContainer checks a container spec against the schema
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
|
||||
return check.CheckObject(container)
|
||||
}
|
||||
|
||||
// CheckObject checks arbitrary data against the schema
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return false, err
|
||||
}
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
}
|
||||
|
||||
// CheckAdditionalObjects looks for an object that passes the specified additional schema
|
||||
func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []interface{}) (bool, error) {
|
||||
val, ok := check.AdditionalValidators[groupkind]
|
||||
if !ok {
|
||||
return false, errors.New("No validator found for " + groupkind)
|
||||
}
|
||||
for _, obj := range objects {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
errs, err := val.ValidateBytes(bytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
errs, err := check.Schema.ValidateBytes(bytes)
|
||||
return len(errs) == 0, err
|
||||
}
|
||||
|
||||
// IsActionable decides if this check applies to a particular target
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
|
||||
if funk.Contains(HandledTargets, target) {
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
} else if string(check.Target) != kind && !strings.HasSuffix(string(check.Target), "/"+kind) {
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool {
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
isIncluded := len(check.Controllers.Include) == 0
|
||||
for _, inclusion := range check.Controllers.Include {
|
||||
if inclusion == kind {
|
||||
if inclusion == controllerType {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
@@ -298,7 +169,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit boo
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Controllers.Exclude {
|
||||
if exclusion == kind {
|
||||
if exclusion == controllerType {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,8 @@
|
||||
padding-right: 40px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
.card.insights img {
|
||||
max-width: 400px;
|
||||
}
|
||||
.card.insights a {
|
||||
background-color: #20162D;
|
||||
|
||||
@@ -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 |
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB |
@@ -130,17 +130,13 @@ func writeTemplate(tmpl *template.Template, data *templateData, w http.ResponseW
|
||||
|
||||
func getConfigForQuery(base config.Configuration, query url.Values) config.Configuration {
|
||||
c := base
|
||||
switch query.Get("disallowExemptions") {
|
||||
case "true":
|
||||
c.DisallowExemptions = true
|
||||
c.DisallowConfigExemptions = true
|
||||
c.DisallowAnnotationExemptions = true
|
||||
default:
|
||||
exemptions := query.Get("disallowExemptions")
|
||||
if exemptions == "false" {
|
||||
c.DisallowExemptions = false
|
||||
c.DisallowConfigExemptions = false
|
||||
c.DisallowAnnotationExemptions = false
|
||||
}
|
||||
|
||||
if exemptions == "true" {
|
||||
c.DisallowExemptions = true
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -177,7 +173,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
adjustedConf := getConfigForQuery(c, r.URL.Query())
|
||||
if auditData == nil {
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
|
||||
@@ -185,7 +181,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
}
|
||||
|
||||
var auditDataObj validator.AuditData
|
||||
auditDataObj, err = validator.RunAudit(adjustedConf, k)
|
||||
auditDataObj, err = validator.RunAudit(adjustedConf, k, false)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -210,7 +206,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
adjustedConf := getConfigForQuery(c, r.URL.Query())
|
||||
|
||||
if auditData == nil {
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
|
||||
@@ -218,7 +214,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
}
|
||||
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(adjustedConf, k)
|
||||
auditData, err = validator.RunAudit(adjustedConf, k, false)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting audit data: %v", err)
|
||||
http.Error(w, "Error running audit", 500)
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWarningWidth(t *testing.T) {
|
||||
input1 := validator.CountSummary{
|
||||
Successes: 0,
|
||||
Warnings: 0,
|
||||
Dangers: 0,
|
||||
}
|
||||
input2 := 6
|
||||
|
||||
expectedOutput := uint(0x6)
|
||||
actual := getWarningWidth(input1, input2)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
|
||||
input1 = validator.CountSummary{
|
||||
Successes: 10,
|
||||
Warnings: 3,
|
||||
Dangers: 1,
|
||||
}
|
||||
input2 = 3
|
||||
|
||||
expectedOutput = uint(0x2)
|
||||
actual = getWarningWidth(input1, input2)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
}
|
||||
func TestSuccessWidth(t *testing.T) {
|
||||
input1 := validator.CountSummary{
|
||||
Successes: 0,
|
||||
Warnings: 0,
|
||||
Dangers: 0,
|
||||
}
|
||||
input2 := 6
|
||||
|
||||
expectedOutput := uint(0x6)
|
||||
actual := getSuccessWidth(input1, input2)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
|
||||
input1 = validator.CountSummary{
|
||||
Successes: 8,
|
||||
Warnings: 6,
|
||||
Dangers: 4,
|
||||
}
|
||||
input2 = 7
|
||||
|
||||
expectedOutput = uint(0x3)
|
||||
actual = getSuccessWidth(input1, input2)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
}
|
||||
|
||||
func TestGetGrade(t *testing.T) {
|
||||
input := validator.CountSummary{
|
||||
Successes: 10,
|
||||
Warnings: 3,
|
||||
Dangers: 1,
|
||||
}
|
||||
expectedOutput := "B-"
|
||||
actual := getGrade(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "A+", actual)
|
||||
}
|
||||
|
||||
func TestGetWeatherIcon(t *testing.T) {
|
||||
input := validator.CountSummary{
|
||||
Successes: 10,
|
||||
Warnings: 3,
|
||||
Dangers: 1,
|
||||
}
|
||||
|
||||
expectedOutput := "fa-cloud-sun"
|
||||
actual := getWeatherIcon(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "fa-cloud-showers-heavy", actual)
|
||||
}
|
||||
|
||||
func TestGetResultClass(t *testing.T) {
|
||||
input := validator.ResultMessage{
|
||||
ID: "",
|
||||
Message: "",
|
||||
Details: []string(nil),
|
||||
Success: false,
|
||||
Severity: "",
|
||||
Category: "",
|
||||
}
|
||||
|
||||
expectedOutput := " failure"
|
||||
actual := getResultClass(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, " success", actual)
|
||||
|
||||
input = validator.ResultMessage{
|
||||
ID: "",
|
||||
Message: "",
|
||||
Details: []string(nil),
|
||||
Success: true,
|
||||
Severity: "",
|
||||
Category: "",
|
||||
}
|
||||
|
||||
expectedOutput = " success"
|
||||
actual = getResultClass(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, " failure", actual)
|
||||
}
|
||||
|
||||
func TestGetWeatherText(t *testing.T) {
|
||||
input := validator.CountSummary{
|
||||
Successes: 10,
|
||||
Warnings: 3,
|
||||
Dangers: 1,
|
||||
}
|
||||
|
||||
expectedOutput := "Mostly smooth sailing"
|
||||
actual := getWeatherText(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "Storms ahead, be careful", actual)
|
||||
}
|
||||
|
||||
func TestGetIcon(t *testing.T) {
|
||||
input := validator.ResultMessage{
|
||||
ID: "",
|
||||
Message: "",
|
||||
Details: []string(nil),
|
||||
Success: false,
|
||||
Severity: "",
|
||||
Category: "",
|
||||
}
|
||||
|
||||
expectedOutput := "fas fa-times"
|
||||
actual := getIcon(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "fas fa-check", actual)
|
||||
|
||||
input = validator.ResultMessage{
|
||||
ID: "",
|
||||
Message: "",
|
||||
Details: []string(nil),
|
||||
Success: true,
|
||||
Severity: "",
|
||||
Category: "",
|
||||
}
|
||||
|
||||
expectedOutput = "fas fa-check"
|
||||
actual = getIcon(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "fas fa-times", actual)
|
||||
|
||||
input = validator.ResultMessage{
|
||||
ID: "",
|
||||
Message: "",
|
||||
Details: []string(nil),
|
||||
Success: false,
|
||||
Severity: config.SeverityWarning,
|
||||
Category: "",
|
||||
}
|
||||
|
||||
expectedOutput = "fas fa-exclamation"
|
||||
actual = getIcon(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "fas fa-times", actual)
|
||||
}
|
||||
|
||||
func TestGetCategoryLink(t *testing.T) {
|
||||
input := "Efficiency"
|
||||
|
||||
expectedOutput := "https://polaris.docs.fairwinds.com/checks/efficiency"
|
||||
actual := getCategoryLink(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "ttps://polaris.docs.fairwinds.com/checks/reliability", actual)
|
||||
}
|
||||
|
||||
func TestGetCategoryInfo(t *testing.T) {
|
||||
input := "Security"
|
||||
|
||||
expectedOutput :=
|
||||
`
|
||||
Kubernetes provides a great deal of configurability when it comes to the
|
||||
security of your workloads. A key principle here involves limiting the level
|
||||
of access any individual workload has. Polaris has validations for a number of
|
||||
best practices, mostly focused on ensuring that unnecessary access has not
|
||||
been granted to an application workload.
|
||||
`
|
||||
actual := getCategoryInfo(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "fas fa-times", actual)
|
||||
|
||||
input = "Reliability"
|
||||
|
||||
expectedOutput =
|
||||
`
|
||||
Kubernetes is built to reliabily run highly available applications.
|
||||
Polaris includes a number of checks to ensure that you are maximizing
|
||||
the reliability potential of Kubernetes.
|
||||
`
|
||||
actual = getCategoryInfo(input)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, "fas fa-times", actual)
|
||||
}
|
||||
|
||||
func TestStringInSlice(t *testing.T) {
|
||||
input1 := "a"
|
||||
input2 := []string{"a", "b", "cde"}
|
||||
|
||||
expectedOutput := true
|
||||
actual := stringInSlice(input1, input2)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, false, actual)
|
||||
|
||||
input1 = "f"
|
||||
input2 = []string{"a", "b", "cde"}
|
||||
|
||||
expectedOutput = false
|
||||
actual = stringInSlice(input1, input2)
|
||||
|
||||
assert.Equal(t, expectedOutput, actual)
|
||||
assert.NotEqual(t, true, actual)
|
||||
}
|
||||
|
||||
@@ -83,12 +83,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card transparent">
|
||||
<a class="align-self-center"
|
||||
target="_blank"
|
||||
href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="static/images/benchmark.png" />
|
||||
</a>
|
||||
<div id="insights" class="card insights py-2">
|
||||
<img class="align-self-center" src="static/images/FW_Insights_Polaris.svg" />
|
||||
<span>Fairwinds, the company behind Polaris, now offers Fairwinds Insights, a multi-cluster, multi-user Kubernetes configuration validation and policy enforcement platform. Fairwinds Insights can help you avoid errors that lead to wasted time, compute costs, and increased risk.</span>
|
||||
<a class="col-auto py-1 px-3 mt-2 align-self-center" href="https://fairwinds.com/insights?source=polaris" target="_blank">Try Insights</a>
|
||||
</div>
|
||||
|
||||
<div id="categories" class="card category">
|
||||
@@ -136,11 +134,7 @@
|
||||
|
||||
{{ range $namespace, $results := .FilteredAuditData.GetResultsByNamespace }}
|
||||
<div id="{{ $namespace }}" class="card namespace">
|
||||
{{ if eq $namespace "" }}
|
||||
<h3>Cluster Resources</h3>
|
||||
{{ else }}
|
||||
<h3>Namespace: <strong>{{ $namespace }}</strong></h3>
|
||||
{{ end }}
|
||||
<div class="expandable-table">
|
||||
{{ range $index, $result := $results }}
|
||||
<div class="resource-info">
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<hr class="col-11">
|
||||
<div class="align-self-center d-flex flex-column justify-content-center">
|
||||
<img class="row mb-4 fw-logo" src="static/images/purple_logo_fairwinds.svg" alt="Fairwinds" />
|
||||
<a class="row justify-content-center" href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">© 2020 Fairwinds Ops Inc.</a>
|
||||
<a class="row justify-content-center" href="https://fairwinds.com?source=polaris" target="_blank">© 2020 Fairwinds Ops Inc.</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="navbar">
|
||||
<div class="fw-nav">
|
||||
<div class="container p-2">
|
||||
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">
|
||||
<a href="https://fairwinds.com?source=polaris" target="_blank">
|
||||
<img class="fw-logo" src="static/images/white_logo_fairwinds.svg" alt="Fairwinds" />
|
||||
</a>
|
||||
<div class="right-section p-0 d-flex justify-content-between">
|
||||
|
||||
+70
-158
@@ -12,15 +12,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/thoas/go-funk"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -37,74 +34,8 @@ type ResourceProvider struct {
|
||||
SourceType string
|
||||
Nodes []corev1.Node
|
||||
Namespaces []corev1.Namespace
|
||||
Resources resourceKindMap
|
||||
}
|
||||
|
||||
type resourceKindMap map[string][]GenericResource
|
||||
|
||||
func (rkm resourceKindMap) addResource(r GenericResource) {
|
||||
gvk := r.Resource.GroupVersionKind()
|
||||
key := gvk.Group + "/" + gvk.Kind
|
||||
rkm[key] = append(rkm[key], r)
|
||||
}
|
||||
|
||||
func (rkm resourceKindMap) addResources(rs []GenericResource) {
|
||||
for _, r := range rs {
|
||||
rkm.addResource(r)
|
||||
}
|
||||
}
|
||||
|
||||
func (rkm resourceKindMap) GetLength() int {
|
||||
total := 0
|
||||
for _, rs := range rkm {
|
||||
total += len(rs)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (rkm resourceKindMap) GetNumberOfControllers() int {
|
||||
total := 0
|
||||
for _, rs := range rkm {
|
||||
for _, r := range rs {
|
||||
if r.PodSpec != nil {
|
||||
total++
|
||||
}
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// This is here for backward compatibility reasons
|
||||
func maybeTransformKindIntoGroupKind(k string) string {
|
||||
if k == "Ingress" {
|
||||
return "networking.k8s.io/Ingress"
|
||||
} else if k == "PodDisruptionBudget" {
|
||||
return "policy/PodDisruptionBudget"
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func parseGroupKind(gk string) schema.GroupKind {
|
||||
i := strings.Index(gk, "/")
|
||||
if i == -1 {
|
||||
return schema.GroupKind{Kind: gk}
|
||||
}
|
||||
|
||||
group := gk[:i]
|
||||
kind := gk[i+1:]
|
||||
return schema.GroupKind{Group: group, Kind: kind}
|
||||
}
|
||||
|
||||
func newResourceProvider(version, sourceType, sourceName string) ResourceProvider {
|
||||
return ResourceProvider{
|
||||
ServerVersion: version,
|
||||
SourceType: sourceType,
|
||||
SourceName: sourceName,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: make([]corev1.Node, 0),
|
||||
Namespaces: make([]corev1.Namespace, 0),
|
||||
Resources: make(map[string][]GenericResource),
|
||||
}
|
||||
Controllers []GenericWorkload
|
||||
Ingresses []v1beta1.Ingress
|
||||
}
|
||||
|
||||
type k8sResource struct {
|
||||
@@ -114,18 +45,18 @@ type k8sResource struct {
|
||||
var podSpecFields = []string{"jobTemplate", "spec", "template"}
|
||||
|
||||
// CreateResourceProvider returns a new ResourceProvider object to interact with k8s resources
|
||||
func CreateResourceProvider(ctx context.Context, directory, workload string, c conf.Configuration) (*ResourceProvider, error) {
|
||||
func CreateResourceProvider(ctx context.Context, directory, workload string) (*ResourceProvider, error) {
|
||||
if workload != "" {
|
||||
return CreateResourceProviderFromResource(ctx, workload)
|
||||
return CreateResourceProviderFromWorkload(ctx, workload)
|
||||
}
|
||||
if directory != "" {
|
||||
return CreateResourceProviderFromPath(directory)
|
||||
}
|
||||
return CreateResourceProviderFromCluster(ctx, c)
|
||||
return CreateResourceProviderFromCluster(ctx)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromResource creates a new ResourceProvider that just contains one workload
|
||||
func CreateResourceProviderFromResource(ctx context.Context, workload string) (*ResourceProvider, error) {
|
||||
// CreateResourceProviderFromWorkload creates a new ResourceProvider that just contains one workload
|
||||
func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
@@ -141,7 +72,14 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
|
||||
logrus.Errorf("Error fetching Cluster API version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
resources := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Resource", workload)
|
||||
resources := ResourceProvider{
|
||||
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
|
||||
SourceType: "Workload",
|
||||
SourceName: workload,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: []corev1.Node{},
|
||||
Namespaces: []corev1.Namespace{},
|
||||
}
|
||||
|
||||
parts := strings.Split(workload, "/")
|
||||
if len(parts) != 4 {
|
||||
@@ -168,24 +106,31 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
|
||||
logrus.Errorf("Could not find workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(*obj)
|
||||
workloadObj, err := NewGenericWorkloadFromUnstructured(kind, obj)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not parse workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources.Resources.addResource(workloadObj)
|
||||
resources.Controllers = []GenericWorkload{workloadObj}
|
||||
return &resources, nil
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromPath returns a new ResourceProvider using the YAML files in a directory
|
||||
func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error) {
|
||||
resources := newResourceProvider("unknown", "Path", directory)
|
||||
resources := ResourceProvider{
|
||||
ServerVersion: "unknown",
|
||||
SourceType: "Path",
|
||||
SourceName: directory,
|
||||
Nodes: []corev1.Node{},
|
||||
Namespaces: []corev1.Namespace{},
|
||||
Controllers: []GenericWorkload{},
|
||||
}
|
||||
|
||||
if directory == "-" {
|
||||
fi, err := os.Stdin.Stat()
|
||||
if err == nil && fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||
if err := resources.addResourcesFromReader(os.Stdin); err != nil {
|
||||
if err := addResourcesFromReader(os.Stdin, &resources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resources, nil
|
||||
@@ -201,7 +146,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
logrus.Errorf("Error reading file: %v", path)
|
||||
return err
|
||||
}
|
||||
return resources.addResourcesFromYaml(string(contents))
|
||||
return addResourcesFromYaml(string(contents), &resources)
|
||||
}
|
||||
|
||||
err := filepath.Walk(directory, visitFile)
|
||||
@@ -212,7 +157,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromCluster creates a new ResourceProvider using live data from a cluster
|
||||
func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration) (*ResourceProvider, error) {
|
||||
func CreateResourceProviderFromCluster(ctx context.Context) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
@@ -228,18 +173,17 @@ func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration
|
||||
logrus.Errorf("Error connecting to dynamic interface: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface, c)
|
||||
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface
|
||||
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface, c conf.Configuration) (*ResourceProvider, error) {
|
||||
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface) (*ResourceProvider, error) {
|
||||
listOpts := metav1.ListOptions{}
|
||||
serverVersion, err := kube.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Cluster API version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
provider := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Cluster", clusterName)
|
||||
|
||||
nodes, err := kube.CoreV1().Nodes().List(ctx, listOpts)
|
||||
if err != nil {
|
||||
@@ -256,6 +200,11 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
logrus.Errorf("Error fetching Pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
ingressList, err := kube.ExtensionsV1beta1().Ingresses("").List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Ingresses: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
|
||||
if err != nil {
|
||||
@@ -263,51 +212,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
return nil, err
|
||||
}
|
||||
restMapper := restmapper.NewDiscoveryRESTMapper(resources)
|
||||
allChecks := []conf.SchemaCheck{}
|
||||
for _, check := range c.CustomChecks {
|
||||
allChecks = append(allChecks, check)
|
||||
}
|
||||
for _, check := range conf.BuiltInChecks {
|
||||
allChecks = append(allChecks, check)
|
||||
}
|
||||
|
||||
var additionalKinds []conf.TargetKind
|
||||
for _, check := range allChecks {
|
||||
neededKinds := []conf.TargetKind{check.Target}
|
||||
for key := range check.AdditionalSchemas {
|
||||
neededKinds = append(neededKinds, conf.TargetKind(key))
|
||||
}
|
||||
for key := range check.AdditionalSchemaStrings {
|
||||
neededKinds = append(neededKinds, conf.TargetKind(key))
|
||||
}
|
||||
for _, kind := range neededKinds {
|
||||
if !funk.Contains(conf.HandledTargets, kind) && !funk.Contains(additionalKinds, kind) {
|
||||
additionalKinds = append(additionalKinds, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, kind := range additionalKinds {
|
||||
groupKind := parseGroupKind(maybeTransformKindIntoGroupKind(string(kind)))
|
||||
mapping, err := (restMapper).RESTMapping(groupKind)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving mapping of Kind %s because of error: %v", kind, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects, err := (*dynamic).Resource(mapping.Resource).Namespace("").List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range objects.Items {
|
||||
res, err := NewGenericResourceFromUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider.Resources.addResource(res)
|
||||
}
|
||||
}
|
||||
|
||||
objectCache := map[string]unstructured.Unstructured{}
|
||||
|
||||
@@ -316,15 +220,23 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
logrus.Errorf("Error loading controllers from pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
provider.Nodes = nodes.Items
|
||||
provider.Namespaces = namespaces.Items
|
||||
provider.Resources.addResources(controllers)
|
||||
return &provider, nil
|
||||
|
||||
api := ResourceProvider{
|
||||
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
|
||||
SourceType: "Cluster",
|
||||
SourceName: clusterName,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: nodes.Items,
|
||||
Namespaces: namespaces.Items,
|
||||
Controllers: controllers,
|
||||
Ingresses: ingressList.Items,
|
||||
}
|
||||
return &api, nil
|
||||
}
|
||||
|
||||
// LoadControllers loads a list of controllers from the kubeResources Pods
|
||||
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
|
||||
interfaces := []GenericResource{}
|
||||
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericWorkload, error) {
|
||||
interfaces := []GenericWorkload{}
|
||||
deduped := map[string]corev1.Pod{}
|
||||
for _, pod := range pods {
|
||||
owners := pod.ObjectMeta.OwnerReferences
|
||||
@@ -335,7 +247,7 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe
|
||||
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod
|
||||
}
|
||||
for _, pod := range deduped {
|
||||
workload, err := ResolveControllerFromPod(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
workload, err := NewGenericWorkload(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -346,8 +258,8 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe
|
||||
|
||||
// Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates.
|
||||
// In cases like CronJobs older children can hang around, so this takes the most recent.
|
||||
func deduplicateControllers(inputControllers []GenericResource) []GenericResource {
|
||||
controllerMap := make(map[string]GenericResource)
|
||||
func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkload {
|
||||
controllerMap := make(map[string]GenericWorkload)
|
||||
for _, controller := range inputControllers {
|
||||
key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName()
|
||||
oldController, ok := controllerMap[key]
|
||||
@@ -355,34 +267,32 @@ func deduplicateControllers(inputControllers []GenericResource) []GenericResourc
|
||||
controllerMap[key] = controller
|
||||
}
|
||||
}
|
||||
results := make([]GenericResource, len(controllerMap))
|
||||
idx := 0
|
||||
results := make([]GenericWorkload, 0)
|
||||
for _, controller := range controllerMap {
|
||||
results[idx] = controller
|
||||
idx++
|
||||
results = append(results, controller)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (resources *ResourceProvider) addResourcesFromReader(reader io.Reader) error {
|
||||
func addResourcesFromReader(reader io.Reader, resources *ResourceProvider) error {
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error reading from %v: %v", reader, err)
|
||||
return err
|
||||
}
|
||||
if err := resources.addResourcesFromYaml(string(contents)); err != nil {
|
||||
if err := addResourcesFromYaml(string(contents), resources); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (resources *ResourceProvider) addResourcesFromYaml(contents string) error {
|
||||
func addResourcesFromYaml(contents string, resources *ResourceProvider) error {
|
||||
specs := regexp.MustCompile("[\r\n]-+[\r\n]").Split(string(contents), -1)
|
||||
for _, spec := range specs {
|
||||
if strings.TrimSpace(spec) == "" {
|
||||
continue
|
||||
}
|
||||
err := resources.addResourceFromString(spec)
|
||||
err := addResourceFromString(spec, resources)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing YAML: (%v)", err)
|
||||
return err
|
||||
@@ -391,7 +301,7 @@ func (resources *ResourceProvider) addResourcesFromYaml(contents string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (resources *ResourceProvider) addResourceFromString(contents string) error {
|
||||
func addResourceFromString(contents string, resources *ResourceProvider) error {
|
||||
contentBytes := []byte(contents)
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
|
||||
resource := k8sResource{}
|
||||
@@ -406,25 +316,27 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
|
||||
ns := corev1.Namespace{}
|
||||
err = decoder.Decode(&ns)
|
||||
resources.Namespaces = append(resources.Namespaces, ns)
|
||||
}
|
||||
|
||||
if resource.Kind == "Pod" {
|
||||
} else if resource.Kind == "Pod" {
|
||||
pod := corev1.Pod{}
|
||||
err = decoder.Decode(&pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workload, err := NewGenericResourceFromPod(pod, pod)
|
||||
workload, err := NewGenericWorkloadFromPod(pod, pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.Resources.addResource(workload)
|
||||
resources.Controllers = append(resources.Controllers, workload)
|
||||
} else if resource.Kind == "Ingress" {
|
||||
ingress := v1beta1.Ingress{}
|
||||
err = decoder.Decode(&ingress)
|
||||
resources.Ingresses = append(resources.Ingresses, ingress)
|
||||
} else {
|
||||
newResource, err := NewGenericResourceFromBytes(contentBytes)
|
||||
if err != nil {
|
||||
newController, err := GetWorkloadFromBytes(contentBytes)
|
||||
if err != nil || newController == nil {
|
||||
return err
|
||||
}
|
||||
resources.Resources.addResource(newResource)
|
||||
resources.Controllers = append(resources.Controllers, *newController)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
+31
-27
@@ -7,34 +7,32 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestGetResourcesFromPath(t *testing.T) {
|
||||
provider, err := CreateResourceProviderFromPath("./test_files/test_1")
|
||||
resources, err := CreateResourceProviderFromPath("./test_files/test_1")
|
||||
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
|
||||
assert.Equal(t, "Path", provider.SourceType, "Should have type Path")
|
||||
assert.Equal(t, "./test_files/test_1", provider.SourceName, "Should have filename as name")
|
||||
assert.Equal(t, "unknown", provider.ServerVersion, "Server version should be unknown")
|
||||
assert.IsType(t, time.Now(), provider.CreationTime, "Creation time should be set")
|
||||
assert.Equal(t, "Path", resources.SourceType, "Should have type Path")
|
||||
assert.Equal(t, "./test_files/test_1", resources.SourceName, "Should have filename as name")
|
||||
assert.Equal(t, "unknown", resources.ServerVersion, "Server version should be unknown")
|
||||
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")
|
||||
|
||||
assert.Equal(t, 0, len(provider.Nodes), "Should not have any nodes")
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
|
||||
assert.Equal(t, 1, len(provider.Namespaces), "Should have a namespace")
|
||||
assert.Equal(t, "two", provider.Namespaces[0].ObjectMeta.Name)
|
||||
assert.Equal(t, 1, len(resources.Namespaces), "Should have a namespace")
|
||||
assert.Equal(t, "two", resources.Namespaces[0].ObjectMeta.Name)
|
||||
|
||||
assert.Equal(t, 9, len(resources.Controllers), "Should have eight controllers")
|
||||
namespaceCount := map[string]int{}
|
||||
for _, resources := range provider.Resources {
|
||||
for _, controller := range resources {
|
||||
namespaceCount[controller.ObjectMeta.GetNamespace()]++
|
||||
}
|
||||
for _, controller := range resources.Controllers {
|
||||
namespaceCount[controller.ObjectMeta.GetNamespace()]++
|
||||
}
|
||||
assert.Equal(t, 11, provider.Resources.GetLength())
|
||||
assert.Equal(t, 10, namespaceCount[""])
|
||||
assert.Equal(t, 8, namespaceCount[""])
|
||||
assert.Equal(t, 1, namespaceCount["two"])
|
||||
}
|
||||
|
||||
@@ -50,8 +48,8 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
|
||||
assert.Equal(t, 1, len(resources.Resources["extensions/Deployment"]), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Resources["extensions/Deployment"][0].PodSpec.Containers[0].Name)
|
||||
assert.Equal(t, 1, len(resources.Controllers), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Controllers[0].PodSpec.Containers[0].Name)
|
||||
|
||||
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
|
||||
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
|
||||
@@ -67,14 +65,21 @@ func TestAddResourcesFromReader(t *testing.T) {
|
||||
contents, err := ioutil.ReadFile("./test_files/test_2/multi.yaml")
|
||||
assert.NoError(t, err)
|
||||
reader := bytes.NewBuffer(contents)
|
||||
resources := newResourceProvider("unknown", "Path", "-")
|
||||
err = resources.addResourcesFromReader(reader)
|
||||
resources := &ResourceProvider{
|
||||
ServerVersion: "unknown",
|
||||
SourceType: "Path",
|
||||
SourceName: "-",
|
||||
Nodes: []corev1.Node{},
|
||||
Namespaces: []corev1.Namespace{},
|
||||
Controllers: []GenericWorkload{},
|
||||
}
|
||||
err = addResourcesFromReader(reader, resources)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
|
||||
assert.Equal(t, 1, len(resources.Resources["extensions/Deployment"]), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Resources["extensions/Deployment"][0].PodSpec.Containers[0].Name)
|
||||
assert.Equal(t, 1, len(resources.Controllers), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Controllers[0].PodSpec.Containers[0].Name)
|
||||
|
||||
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
|
||||
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
|
||||
@@ -83,7 +88,7 @@ func TestAddResourcesFromReader(t *testing.T) {
|
||||
|
||||
func TestGetResourceFromAPI(t *testing.T) {
|
||||
k8s, dynamicInterface := test.SetupTestAPI(test.GetMockControllers("test")...)
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface, conf.Configuration{})
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface)
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
|
||||
assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path")
|
||||
@@ -91,7 +96,8 @@ func TestGetResourceFromAPI(t *testing.T) {
|
||||
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")
|
||||
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
|
||||
assert.Equal(t, 0, len(resources.Ingresses), "Should not have any ingresses")
|
||||
assert.Equal(t, 5, len(resources.Controllers), "Should have 5 controllers")
|
||||
|
||||
expectedNames := map[string]bool{
|
||||
"deploy": false,
|
||||
@@ -100,10 +106,8 @@ func TestGetResourceFromAPI(t *testing.T) {
|
||||
"statefulset": false,
|
||||
"daemonset": false,
|
||||
}
|
||||
for _, controllers := range resources.Resources {
|
||||
for _, ctrl := range controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
}
|
||||
for _, ctrl := range resources.Controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
}
|
||||
for name, val := range expectedNames {
|
||||
assert.Equal(t, true, val, name)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -12,62 +13,61 @@ import (
|
||||
kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
// GenericResource is a base implementation with some free methods for inherited structs
|
||||
type GenericResource struct {
|
||||
// GenericWorkload is a base implementation with some free methods for inherited structs
|
||||
type GenericWorkload struct {
|
||||
Kind string
|
||||
PodSpec kubeAPICoreV1.PodSpec
|
||||
ObjectMeta kubeAPIMetaV1.Object
|
||||
Resource unstructured.Unstructured
|
||||
PodSpec *kubeAPICoreV1.PodSpec
|
||||
OriginalObjectJSON []byte
|
||||
}
|
||||
|
||||
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
|
||||
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured) (GenericResource, error) {
|
||||
workload := GenericResource{
|
||||
Kind: unst.GetKind(),
|
||||
Resource: unst,
|
||||
// NewGenericWorkloadFromUnstructured creates a workload from an unstructured.Unstructured
|
||||
func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload := GenericWorkload{
|
||||
Kind: kind,
|
||||
}
|
||||
|
||||
objMeta, err := meta.Accessor(&unst)
|
||||
objMeta, err := meta.Accessor(unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.ObjectMeta = objMeta
|
||||
|
||||
b, err := json.Marshal(&unst)
|
||||
b, err := json.Marshal(unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = b
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
podSpecMap := GetPodSpec(m)
|
||||
if podSpecMap != nil {
|
||||
b, err = json.Marshal(podSpecMap)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
podSpec := kubeAPICoreV1.PodSpec{}
|
||||
err = json.Unmarshal(b, &podSpec)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.PodSpec = &podSpec
|
||||
b, err = json.Marshal(podSpecMap)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
podSpec := kubeAPICoreV1.PodSpec{}
|
||||
err = json.Unmarshal(b, &podSpec)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.PodSpec = podSpec
|
||||
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents
|
||||
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) {
|
||||
workload := GenericResource{
|
||||
// NewGenericWorkloadFromPod builds a new workload for a given Pod without looking at parents
|
||||
func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericWorkload, error) {
|
||||
workload := GenericWorkload{
|
||||
Kind: "Pod",
|
||||
PodSpec: &podResource.Spec,
|
||||
PodSpec: podResource.Spec,
|
||||
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
|
||||
}
|
||||
if originalObject != nil {
|
||||
@@ -77,12 +77,13 @@ func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject int
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
|
||||
err = json.Unmarshal(bytes, &workload.Resource.Object)
|
||||
var unst unstructured.Unstructured
|
||||
err = json.Unmarshal(bytes, &unst.Object)
|
||||
if err != nil {
|
||||
logrus.Error("Couldn't marshal JSON for pod ", err)
|
||||
return workload, err
|
||||
}
|
||||
objMeta, err := meta.Accessor(&workload.Resource)
|
||||
objMeta, err := meta.Accessor(&unst)
|
||||
if err != nil {
|
||||
logrus.Error("Couldn't create meta accessor for unstructred ", err)
|
||||
return workload, err
|
||||
@@ -92,36 +93,26 @@ func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject int
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
// NewGenericResourceFromBytes parses a generic kubernetes resource
|
||||
func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
|
||||
unst := unstructured.Unstructured{}
|
||||
err := yaml.Unmarshal(contentBytes, &unst.Object)
|
||||
if err != nil {
|
||||
return GenericResource{}, err
|
||||
}
|
||||
return NewGenericResourceFromUnstructured(unst)
|
||||
}
|
||||
|
||||
// ResolveControllerFromPod builds a new workload for a given Pod
|
||||
func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
|
||||
workload, err := resolveControllerFromPod(ctx, podResource, dynamicClient, restMapper, objectCache)
|
||||
// NewGenericWorkload builds a new workload for a given Pod
|
||||
func NewGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload, err := newGenericWorkload(ctx, podResource, dynamicClient, restMapper, objectCache)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
if len(workload.OriginalObjectJSON) == 0 {
|
||||
return NewGenericResourceFromPod(podResource, podResource)
|
||||
return NewGenericWorkloadFromPod(podResource, podResource)
|
||||
}
|
||||
return workload, err
|
||||
}
|
||||
|
||||
func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
|
||||
podWorkload, err := NewGenericResourceFromPod(podResource, nil)
|
||||
func newGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload, err := NewGenericWorkloadFromPod(podResource, nil)
|
||||
if err != nil {
|
||||
return podWorkload, err
|
||||
return workload, err
|
||||
}
|
||||
topKind := "Pod"
|
||||
topMeta := podWorkload.ObjectMeta
|
||||
owners := podResource.ObjectMeta.GetOwnerReferences()
|
||||
// If an owner exists then set the name to the workload.
|
||||
// This allows us to handle CRDs creating Workloads or DeploymentConfigs in OpenShift.
|
||||
owners := workload.ObjectMeta.GetOwnerReferences()
|
||||
lastKey := ""
|
||||
for len(owners) > 0 {
|
||||
if len(owners) > 1 {
|
||||
@@ -131,12 +122,12 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
if firstOwner.Kind == "Node" {
|
||||
break
|
||||
}
|
||||
topKind = firstOwner.Kind
|
||||
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, topMeta.GetNamespace(), firstOwner.Name)
|
||||
workload.Kind = firstOwner.Kind
|
||||
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, workload.ObjectMeta.GetNamespace(), firstOwner.Name)
|
||||
lastKey = key
|
||||
abstractObject, ok := objectCache[key]
|
||||
if !ok {
|
||||
err := cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
|
||||
err = cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error caching objects of Kind %s %v", firstOwner.Kind, err)
|
||||
break
|
||||
@@ -151,22 +142,26 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
objMeta, err := meta.Accessor(&abstractObject)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent metadata %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err)
|
||||
return GenericResource{}, err
|
||||
return workload, err
|
||||
}
|
||||
topMeta = objMeta
|
||||
workload.ObjectMeta = objMeta
|
||||
owners = abstractObject.GetOwnerReferences()
|
||||
}
|
||||
|
||||
if lastKey != "" {
|
||||
unst := objectCache[lastKey]
|
||||
return NewGenericResourceFromUnstructured(unst)
|
||||
bytes, err := json.Marshal(&unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
} else {
|
||||
bytes, err := json.Marshal(podResource)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
}
|
||||
workload, err := NewGenericResourceFromPod(podResource, podResource)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.Kind = topKind
|
||||
workload.ObjectMeta = topMeta
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
@@ -212,3 +207,37 @@ func GetPodSpec(yaml map[string]interface{}) interface{} {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWorkloadFromBytes parses a GenericWorkload
|
||||
func GetWorkloadFromBytes(contentBytes []byte) (*GenericWorkload, error) {
|
||||
yamlNode := make(map[string]interface{})
|
||||
err := yaml.Unmarshal(contentBytes, &yamlNode)
|
||||
if err != nil {
|
||||
logrus.Errorf("Invalid YAML: %s", string(contentBytes))
|
||||
return nil, err
|
||||
}
|
||||
finalDoc := make(map[string]interface{})
|
||||
finalDoc["metadata"] = yamlNode["metadata"]
|
||||
finalDoc["apiVersion"] = "v1"
|
||||
finalDoc["kind"] = "Pod"
|
||||
podSpec := GetPodSpec(yamlNode)
|
||||
if podSpec == nil {
|
||||
return nil, nil
|
||||
}
|
||||
finalDoc["spec"] = podSpec
|
||||
marshaledYaml, err := yaml.Marshal(finalDoc)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not marshal yaml: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000)
|
||||
pod := kubeAPICoreV1.Pod{}
|
||||
err = decoder.Decode(&pod)
|
||||
newController, err := NewGenericWorkloadFromPod(pod, yamlNode)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newController.Kind = yamlNode["kind"].(string)
|
||||
return &newController, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ValidateContainer validates a single container from a given controller
|
||||
func ValidateContainer(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ContainerResult, error) {
|
||||
results, err := applyContainerSchemaChecks(conf, controller, container, isInit)
|
||||
if err != nil {
|
||||
return ContainerResult{}, err
|
||||
}
|
||||
|
||||
cRes := ContainerResult{
|
||||
Name: container.Name,
|
||||
Results: results,
|
||||
}
|
||||
|
||||
return cRes, nil
|
||||
}
|
||||
|
||||
// ValidateAllContainers validates both init and regular containers
|
||||
func ValidateAllContainers(conf *config.Configuration, controller kube.GenericWorkload) ([]ContainerResult, error) {
|
||||
results := []ContainerResult{}
|
||||
pod := controller.PodSpec
|
||||
for _, container := range pod.InitContainers {
|
||||
result, err := ValidateContainer(conf, controller, &container, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
for _, container := range pod.Containers {
|
||||
result, err := ValidateContainer(conf, controller, &container, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
@@ -50,8 +50,8 @@ exemptions:
|
||||
- foo
|
||||
`
|
||||
|
||||
func getEmptyWorkload(t *testing.T, name string) kube.GenericResource {
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{
|
||||
func getEmptyWorkload(t *testing.T, name string) kube.GenericWorkload {
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
@@ -64,28 +64,25 @@ func testValidate(t *testing.T, container *corev1.Container, resourceConf *strin
|
||||
testValidateWithWorkload(t, container, resourceConf, getEmptyWorkload(t, controllerName), expectedDangers, expectedWarnings, expectedSuccesses)
|
||||
}
|
||||
|
||||
func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericResource, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) {
|
||||
func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericWorkload, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) {
|
||||
parsedConf, err := conf.Parse([]byte(*resourceConf))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
var results ResultSet
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, nil, workload, container, false)
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, workload, container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
summary := results.GetSummary()
|
||||
|
||||
if assert.Equal(t, uint(len(expectedWarnings)), summary.Warnings) {
|
||||
assert.ElementsMatch(t, expectedWarnings, results.GetWarnings())
|
||||
}
|
||||
assert.Equal(t, uint(len(expectedWarnings)), summary.Warnings)
|
||||
assert.ElementsMatch(t, expectedWarnings, results.GetWarnings())
|
||||
|
||||
if assert.Equal(t, uint(len(expectedDangers)), summary.Dangers) {
|
||||
assert.ElementsMatch(t, expectedDangers, results.GetDangers())
|
||||
}
|
||||
assert.Equal(t, uint(len(expectedDangers)), summary.Dangers)
|
||||
assert.ElementsMatch(t, expectedDangers, results.GetDangers())
|
||||
|
||||
if assert.Equal(t, uint(len(expectedSuccesses)), summary.Successes) {
|
||||
assert.ElementsMatch(t, expectedSuccesses, results.GetSuccesses())
|
||||
}
|
||||
assert.Equal(t, uint(len(expectedSuccesses)), summary.Successes)
|
||||
assert.ElementsMatch(t, expectedSuccesses, results.GetSuccesses())
|
||||
}
|
||||
|
||||
func TestValidateResourcesEmptyConfig(t *testing.T) {
|
||||
@@ -93,7 +90,7 @@ func TestValidateResourcesEmptyConfig(t *testing.T) {
|
||||
Name: "Empty",
|
||||
}
|
||||
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{}, nil, getEmptyWorkload(t, ""), container, false)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{}, getEmptyWorkload(t, ""), container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -190,7 +187,7 @@ func TestValidateHealthChecks(t *testing.T) {
|
||||
for idx, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.probes}, nil, controller, tt.container, tt.isInit)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.probes}, controller, tt.container, tt.isInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -304,7 +301,7 @@ func TestValidateImage(t *testing.T) {
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.image}, nil, controller, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.image}, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -421,7 +418,7 @@ func TestValidateNetworking(t *testing.T) {
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.networkConf}, nil, controller, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.networkConf}, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -924,9 +921,9 @@ func TestValidateSecurity(t *testing.T) {
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, nil, workload, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1069,9 +1066,9 @@ func TestValidateRunAsRoot(t *testing.T) {
|
||||
}
|
||||
for idx, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&config, nil, workload, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(&config, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1171,7 +1168,7 @@ func TestValidateResourcesEmptyContainerCPURequestsExempt(t *testing.T) {
|
||||
|
||||
expectedSuccesses := []ResultMessage{}
|
||||
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Annotations: map[string]string{
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
)
|
||||
|
||||
// ValidateController validates a single controller, returns a Result.
|
||||
func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (Result, error) {
|
||||
podResult, err := ValidatePod(conf, controller)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
var controllerResult ResultSet
|
||||
controllerResult, err = applyControllerSchemaChecks(conf, controller)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
result := Result{
|
||||
Kind: controller.Kind,
|
||||
Name: controller.ObjectMeta.GetName(),
|
||||
Namespace: controller.ObjectMeta.GetNamespace(),
|
||||
Results: controllerResult,
|
||||
PodResult: &podResult,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ValidateControllers validates that each deployment conforms to the Polaris config,
|
||||
// builds a list of ResourceResults organized by namespace.
|
||||
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
|
||||
controllersToAudit := kubeResources.Controllers
|
||||
|
||||
results := []Result{}
|
||||
for _, controller := range controllersToAudit {
|
||||
result, err := ValidateController(config, controller)
|
||||
if err != nil {
|
||||
logrus.Warn("An error occurred validating controller:", err)
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func TestValidateController(t *testing.T) {
|
||||
"hostPIDSet": conf.SeverityDanger,
|
||||
},
|
||||
}
|
||||
deployment, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
|
||||
deployment, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
|
||||
assert.NoError(t, err)
|
||||
deployment.Kind = "Deployment"
|
||||
expectedSum := CountSummary{
|
||||
@@ -49,7 +49,7 @@ func TestValidateController(t *testing.T) {
|
||||
}
|
||||
|
||||
var actualResult Result
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, deployment)
|
||||
actualResult, err = ValidateController(&c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -64,39 +64,41 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
testResources := func(res *kube.ResourceProvider) {
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"deploymentMissingReplicas": conf.SeverityDanger,
|
||||
"multipleReplicasForDeployment": conf.SeverityDanger,
|
||||
},
|
||||
}
|
||||
expectedResult := ResultMessage{
|
||||
ID: "deploymentMissingReplicas",
|
||||
ID: "multipleReplicasForDeployment",
|
||||
Severity: "danger",
|
||||
Category: "Reliability",
|
||||
}
|
||||
for _, controller := range res.Resources["Deployment"] {
|
||||
actualResult, err := applyControllerSchemaChecks(&c, nil, controller)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if controller.ObjectMeta.GetName() == "test-deployment-2" {
|
||||
expectedResult.Success = true
|
||||
expectedResult.Message = "Multiple replicas are scheduled"
|
||||
} else if controller.ObjectMeta.GetName() == "test-deployment" {
|
||||
expectedResult.Success = false
|
||||
expectedResult.Message = "Only one replica is scheduled"
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"deploymentMissingReplicas": expectedResult,
|
||||
}
|
||||
for _, controller := range res.Controllers {
|
||||
if controller.Kind == "Deployment" {
|
||||
actualResult, err := ValidateController(&c, controller)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if controller.ObjectMeta.GetName() == "test-deployment-2" {
|
||||
expectedResult.Success = true
|
||||
expectedResult.Message = "Multiple replicas are scheduled"
|
||||
} else if controller.ObjectMeta.GetName() == "test-deployment" {
|
||||
expectedResult.Success = false
|
||||
expectedResult.Message = "Only one replica is scheduled"
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"multipleReplicasForDeployment": expectedResult,
|
||||
}
|
||||
|
||||
assert.Equal(t, "Deployment", actualResult.Kind)
|
||||
assert.Equal(t, 1, len(actualResult.Results), "should be equal")
|
||||
assert.EqualValues(t, expectedResults, actualResult.Results, controller.ObjectMeta.GetName())
|
||||
assert.Equal(t, "Deployment", actualResult.Kind)
|
||||
assert.Equal(t, 1, len(actualResult.Results), "should be equal")
|
||||
assert.EqualValues(t, expectedResults, actualResult.Results, controller.ObjectMeta.GetName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res, err := kube.CreateResourceProviderFromPath("../kube/test_files/test_1")
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
assert.Equal(t, 11, res.Resources.GetLength())
|
||||
assert.Equal(t, 9, len(res.Controllers), "Should have eight controllers")
|
||||
testResources(res)
|
||||
|
||||
replicaSpec := map[string]interface{}{"replicas": 2}
|
||||
@@ -109,9 +111,9 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
two := int32(2)
|
||||
d2.Spec.Replicas = &two
|
||||
k8s, dynamicClient := test.SetupTestAPI(&d1, &p1, &d2, &p2)
|
||||
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, conf.Configuration{})
|
||||
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, 2, res.Resources.GetLength(), "Should have two controllers")
|
||||
assert.Equal(t, 2, len(res.Controllers), "Should have two controllers")
|
||||
testResources(res)
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
}
|
||||
pod := test.MockPod()
|
||||
pod.Spec.InitContainers = []corev1.Container{test.MockContainer("test")}
|
||||
deployment, err := kube.NewGenericResourceFromPod(pod, nil)
|
||||
deployment, err := kube.NewGenericWorkloadFromPod(pod, nil)
|
||||
assert.NoError(t, err)
|
||||
deployment.Kind = "Deployment"
|
||||
expectedSum := CountSummary{
|
||||
@@ -137,7 +139,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
"livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Reliability"},
|
||||
}
|
||||
var actualResult Result
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, deployment)
|
||||
actualResult, err = ValidateController(&c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -147,7 +149,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
assert.EqualValues(t, ResultSet{}, actualResult.PodResult.ContainerResults[0].Results)
|
||||
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[1].Results)
|
||||
|
||||
job, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
|
||||
job, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
|
||||
assert.NoError(t, err)
|
||||
job.Kind = "Job"
|
||||
expectedSum = CountSummary{
|
||||
@@ -156,7 +158,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, job)
|
||||
actualResult, err = ValidateController(&c, job)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -165,7 +167,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[0].Results)
|
||||
|
||||
cronjob, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
|
||||
cronjob, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
|
||||
assert.NoError(t, err)
|
||||
cronjob.Kind = "CronJob"
|
||||
expectedSum = CountSummary{
|
||||
@@ -174,7 +176,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, cronjob)
|
||||
actualResult, err = ValidateController(&c, cronjob)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -201,16 +203,18 @@ func TestControllerExemptions(t *testing.T) {
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
var actualResults []Result
|
||||
|
||||
pod := test.MockPod()
|
||||
pod.ObjectMeta.Namespace = "foo"
|
||||
workload, err := kube.NewGenericResourceFromPod(pod, nil)
|
||||
workload, err := kube.NewGenericWorkloadFromPod(pod, nil)
|
||||
assert.NoError(t, err)
|
||||
workload.Kind = "Deployment"
|
||||
resources := []kube.GenericResource{workload}
|
||||
resources := &kube.ResourceProvider{
|
||||
Controllers: []kube.GenericWorkload{workload},
|
||||
}
|
||||
|
||||
var actualResults []Result
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -221,7 +225,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
c.Exemptions = []conf.Exemption{{
|
||||
Namespace: "foo",
|
||||
}}
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -230,10 +234,10 @@ func TestControllerExemptions(t *testing.T) {
|
||||
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
|
||||
|
||||
c.Exemptions = nil
|
||||
resources[0].ObjectMeta.SetAnnotations(map[string]string{
|
||||
resources.Controllers[0].ObjectMeta.SetAnnotations(map[string]string{
|
||||
exemptionAnnotationKey: "true",
|
||||
})
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -242,7 +246,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
|
||||
|
||||
c.DisallowExemptions = true
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -16,16 +16,23 @@ import (
|
||||
)
|
||||
|
||||
// RunAudit runs a full Polaris audit and returns an AuditData object
|
||||
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
|
||||
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, onlyShowFailedTests bool) (AuditData, error) {
|
||||
displayName := config.DisplayName
|
||||
if displayName == "" {
|
||||
displayName = kubeResources.SourceName
|
||||
}
|
||||
|
||||
results, err := ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
|
||||
results, err := ValidateControllers(&config, kubeResources)
|
||||
if err != nil {
|
||||
return AuditData{}, err
|
||||
}
|
||||
controllerCount := len(results)
|
||||
|
||||
ingressResults, err := ValidateIngresses(&config, kubeResources)
|
||||
if err != nil {
|
||||
return AuditData{}, err
|
||||
}
|
||||
results = append(results, ingressResults...)
|
||||
|
||||
auditData := AuditData{
|
||||
PolarisOutputVersion: PolarisOutputVersion,
|
||||
@@ -36,12 +43,16 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
|
||||
ClusterInfo: ClusterInfo{
|
||||
Version: kubeResources.ServerVersion,
|
||||
Nodes: len(kubeResources.Nodes),
|
||||
Pods: len(kubeResources.Controllers), // TODO validate that this is still valuable
|
||||
Namespaces: len(kubeResources.Namespaces),
|
||||
Controllers: kubeResources.Resources.GetNumberOfControllers(),
|
||||
Controllers: controllerCount,
|
||||
},
|
||||
Results: results,
|
||||
}
|
||||
auditData.Score = auditData.GetSummary().GetScore()
|
||||
if onlyShowFailedTests {
|
||||
auditData.RemoveSuccessfulResults()
|
||||
}
|
||||
return auditData, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ import (
|
||||
)
|
||||
|
||||
func TestGetTemplateData(t *testing.T) {
|
||||
k8s, dynamicClient := test.SetupTestAPI(test.GetMockControllers("test")...)
|
||||
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, 5, len(resources.Controllers))
|
||||
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"readinessProbeMissing": conf.SeverityDanger,
|
||||
@@ -18,11 +23,6 @@ func TestGetTemplateData(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
k8s, dynamicClient := test.SetupTestAPI(test.GetMockControllers("test")...)
|
||||
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, c)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, 5, len(resources.Resources))
|
||||
|
||||
sum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(3),
|
||||
@@ -31,7 +31,7 @@ func TestGetTemplateData(t *testing.T) {
|
||||
score := uint(0)
|
||||
|
||||
var actualAudit AuditData
|
||||
actualAudit, err = RunAudit(c, resources)
|
||||
actualAudit, err = RunAudit(c, resources, false)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, score, actualAudit.Score, "")
|
||||
assert.EqualValues(t, sum, actualAudit.GetSummary())
|
||||
@@ -57,9 +57,8 @@ func TestGetTemplateData(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
if assert.Equal(t, 1, len(result.PodResult.ContainerResults), "bad container results for "+result.Kind) {
|
||||
assert.Equal(t, expected.results, len(result.PodResult.ContainerResults[0].Results))
|
||||
}
|
||||
assert.Equal(t, 1, len(result.PodResult.ContainerResults))
|
||||
assert.Equal(t, expected.results, len(result.PodResult.ContainerResults[0].Results))
|
||||
}
|
||||
assert.Equal(t, found, true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
// ValidateIngresses validates all the ingresses in a ResourceProvider
|
||||
func ValidateIngresses(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
|
||||
var results []Result
|
||||
for _, ingress := range kubeResources.Ingresses {
|
||||
result, err := ValidateIngress(config, ingress)
|
||||
if err != nil {
|
||||
return []Result{}, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ValidateIngress validates a single ingress
|
||||
func ValidateIngress(config *conf.Configuration, ingress v1beta1.Ingress) (Result, error) {
|
||||
results, err := applyIngressSchemaChecks(config, ingress)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
result := Result{
|
||||
Kind: "Ingress",
|
||||
Name: ingress.ObjectMeta.GetName(),
|
||||
Namespace: ingress.ObjectMeta.GetNamespace(),
|
||||
Results: results,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -15,75 +15,50 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
network "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
extv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
func TestValidatePDB(t *testing.T) {
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"pdbDisruptionsIsZero": conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
pdb := unstructured.Unstructured{}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(pdb)
|
||||
res.Kind = "PodDisruptionBudget"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
results := actualResult.Results["pdbDisruptionsIsZero"]
|
||||
|
||||
assert.False(t, results.Success)
|
||||
assert.Equal(t, conf.SeverityWarning, results.Severity)
|
||||
assert.Equal(t, "Reliability", results.Category)
|
||||
assert.EqualValues(t, "Voluntary evictions are not possible", results.Message)
|
||||
}
|
||||
|
||||
func TestValidateIngress(t *testing.T) {
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"tlsSettingsMissing": conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
tls := network.IngressTLS{
|
||||
Hosts: []string{"test"},
|
||||
SecretName: "secret",
|
||||
}
|
||||
ingress := test.MockIngress()
|
||||
|
||||
ingress := network.Ingress{}
|
||||
ingress.Spec.TLS = []network.IngressTLS{tls}
|
||||
b, err := json.Marshal(ingress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
unst := unstructured.Unstructured{}
|
||||
err = json.Unmarshal(b, &unst.Object)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(unst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Kind = "Ingress"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
var actualResult Result
|
||||
actualResult, err := ValidateIngress(&c, ingress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
results := actualResult.Results["tlsSettingsMissing"]
|
||||
|
||||
assert.False(t, results.Success)
|
||||
assert.Equal(t, conf.Severity("warning"), results.Severity)
|
||||
assert.Equal(t, "Security", results.Category)
|
||||
assert.EqualValues(t, "Ingress does not have TLS configured", results.Message)
|
||||
|
||||
tls := extv1beta1.IngressTLS{
|
||||
Hosts: []string{"test"},
|
||||
SecretName: "secret",
|
||||
}
|
||||
|
||||
ingress.Spec.TLS = []extv1beta1.IngressTLS{tls}
|
||||
actualResult, err = ValidateIngress(&c, ingress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
results = actualResult.Results["tlsSettingsMissing"]
|
||||
|
||||
assert.True(t, results.Success)
|
||||
assert.Equal(t, conf.SeverityWarning, results.Severity)
|
||||
assert.Equal(t, conf.Severity("warning"), results.Severity)
|
||||
assert.Equal(t, "Security", results.Category)
|
||||
assert.EqualValues(t, "Ingress has TLS configured", results.Message)
|
||||
|
||||
}
|
||||
+21
-37
@@ -19,7 +19,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/thoas/go-funk"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
)
|
||||
@@ -53,12 +52,10 @@ type AuditData struct {
|
||||
}
|
||||
|
||||
// RemoveSuccessfulResults remove all test that have passed.
|
||||
func (res AuditData) RemoveSuccessfulResults() AuditData {
|
||||
resCopy := res
|
||||
resCopy.Results = funk.Map(res.Results, func(auditDataResult Result) Result {
|
||||
return auditDataResult.removeSuccessfulResults()
|
||||
}).([]Result)
|
||||
return resCopy
|
||||
func (res *AuditData) RemoveSuccessfulResults() {
|
||||
for _, auditDataResult := range res.Results {
|
||||
auditDataResult.removeSuccessfulResults()
|
||||
}
|
||||
}
|
||||
|
||||
// ClusterInfo contains Polaris results as well as some high-level stats
|
||||
@@ -74,7 +71,6 @@ type ClusterInfo struct {
|
||||
type ResultMessage struct {
|
||||
ID string
|
||||
Message string
|
||||
Details []string
|
||||
Success bool
|
||||
Severity config.Severity
|
||||
Category string
|
||||
@@ -83,14 +79,12 @@ type ResultMessage struct {
|
||||
// ResultSet contiains the results for a set of checks
|
||||
type ResultSet map[string]ResultMessage
|
||||
|
||||
func (res ResultSet) removeSuccessfulResults() ResultSet {
|
||||
newResults := ResultSet{}
|
||||
func (res ResultSet) removeSuccessfulResults() {
|
||||
for k, resultMessage := range res {
|
||||
if !resultMessage.Success {
|
||||
newResults[k] = resultMessage
|
||||
if resultMessage.Success {
|
||||
delete(res, k)
|
||||
}
|
||||
}
|
||||
return newResults
|
||||
}
|
||||
|
||||
// Result provides results for a Kubernetes object
|
||||
@@ -103,14 +97,9 @@ type Result struct {
|
||||
CreatedTime time.Time
|
||||
}
|
||||
|
||||
func (res Result) removeSuccessfulResults() Result {
|
||||
resCopy := res
|
||||
resCopy.Results = res.Results.removeSuccessfulResults()
|
||||
if res.PodResult != nil {
|
||||
podCopy := res.PodResult.removeSuccessfulResults()
|
||||
resCopy.PodResult = &podCopy
|
||||
}
|
||||
return resCopy
|
||||
func (res *Result) removeSuccessfulResults() {
|
||||
res.Results.removeSuccessfulResults()
|
||||
res.PodResult.removeSuccessfulResults()
|
||||
}
|
||||
|
||||
// PodResult provides a list of validation messages for each pod.
|
||||
@@ -120,13 +109,14 @@ type PodResult struct {
|
||||
ContainerResults []ContainerResult
|
||||
}
|
||||
|
||||
func (res PodResult) removeSuccessfulResults() PodResult {
|
||||
resCopy := PodResult{}
|
||||
resCopy.Results = res.Results.removeSuccessfulResults()
|
||||
resCopy.ContainerResults = funk.Map(res.ContainerResults, func(containerResult ContainerResult) ContainerResult {
|
||||
return containerResult.removeSuccessfulResults()
|
||||
}).([]ContainerResult)
|
||||
return resCopy
|
||||
func (res *PodResult) removeSuccessfulResults() {
|
||||
if res == nil {
|
||||
return
|
||||
}
|
||||
res.Results.removeSuccessfulResults()
|
||||
for _, containerResult := range res.ContainerResults {
|
||||
containerResult.removeSuccessfulResults()
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerResult provides a list of validation messages for each container.
|
||||
@@ -135,10 +125,8 @@ type ContainerResult struct {
|
||||
Results ResultSet
|
||||
}
|
||||
|
||||
func (res ContainerResult) removeSuccessfulResults() ContainerResult {
|
||||
resCopy := res
|
||||
resCopy.Results = res.Results.removeSuccessfulResults()
|
||||
return resCopy
|
||||
func (res *ContainerResult) removeSuccessfulResults() {
|
||||
res.Results.removeSuccessfulResults()
|
||||
}
|
||||
|
||||
func fillString(id string, l int) string {
|
||||
@@ -164,11 +152,7 @@ func (res AuditData) GetPrettyOutput(useColor bool) string {
|
||||
|
||||
// GetPrettyOutput returns a human-readable string
|
||||
func (res Result) GetPrettyOutput() string {
|
||||
str := titleColor.Sprint(fmt.Sprintf("%s %s", res.Kind, res.Name))
|
||||
if res.Namespace != "" {
|
||||
str += titleColor.Sprint(fmt.Sprintf(" in namespace %s", res.Namespace))
|
||||
}
|
||||
str += "\n"
|
||||
str := titleColor.Sprint(fmt.Sprintf("%s %s in namespace %s\n", res.Kind, res.Name, res.Namespace))
|
||||
str += res.Results.GetPrettyOutput()
|
||||
if res.PodResult != nil {
|
||||
str += res.PodResult.GetPrettyOutput()
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
)
|
||||
|
||||
// ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult.
|
||||
func ValidatePod(conf *config.Configuration, controller kube.GenericWorkload) (PodResult, error) {
|
||||
podResults, err := applyPodSchemaChecks(conf, controller)
|
||||
if err != nil {
|
||||
return PodResult{}, err
|
||||
}
|
||||
pRes := PodResult{
|
||||
Results: podResults,
|
||||
ContainerResults: []ContainerResult{},
|
||||
}
|
||||
|
||||
pRes.ContainerResults, err = ValidateAllContainers(conf, controller)
|
||||
if err != nil {
|
||||
return pRes, err
|
||||
}
|
||||
return pRes, nil
|
||||
}
|
||||
+25
-20
@@ -36,7 +36,7 @@ func TestValidatePod(t *testing.T) {
|
||||
}
|
||||
|
||||
p := test.MockPod()
|
||||
deployment, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
deployment, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(4),
|
||||
@@ -50,14 +50,15 @@ func TestValidatePod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, deployment)
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
}
|
||||
|
||||
func TestInvalidIPCPod(t *testing.T) {
|
||||
@@ -72,7 +73,7 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
|
||||
p := test.MockPod()
|
||||
p.Spec.HostIPC = true
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -85,14 +86,15 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
}
|
||||
|
||||
func TestInvalidNetworkPod(t *testing.T) {
|
||||
@@ -107,7 +109,7 @@ func TestInvalidNetworkPod(t *testing.T) {
|
||||
|
||||
p := test.MockPod()
|
||||
p.Spec.HostNetwork = true
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -121,14 +123,15 @@ func TestInvalidNetworkPod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
}
|
||||
|
||||
func TestInvalidPIDPod(t *testing.T) {
|
||||
@@ -143,7 +146,7 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
|
||||
p := test.MockPod()
|
||||
p.Spec.HostPID = true
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -157,14 +160,15 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
}
|
||||
|
||||
func TestExemption(t *testing.T) {
|
||||
@@ -188,7 +192,7 @@ func TestExemption(t *testing.T) {
|
||||
p.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
}
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -200,12 +204,13 @@ func TestExemption(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
}
|
||||
|
||||
+155
-207
@@ -1,70 +1,112 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/qri-io/jsonschema"
|
||||
"github.com/thoas/go-funk"
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
)
|
||||
|
||||
type schemaTestCase struct {
|
||||
Target config.TargetKind
|
||||
Resource kube.GenericResource
|
||||
IsInitContianer bool
|
||||
Container *corev1.Container
|
||||
ResourceProvider *kube.ResourceProvider
|
||||
var (
|
||||
schemaBox = (*packr.Box)(nil)
|
||||
builtInChecks = map[string]config.SchemaCheck{}
|
||||
// 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",
|
||||
// Ingress checks
|
||||
"tlsSettingsMissing",
|
||||
}
|
||||
)
|
||||
|
||||
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(contents)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
check.ID = checkID
|
||||
builtInChecks[checkID] = check
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) {
|
||||
if !conf.DisallowExemptions &&
|
||||
!conf.DisallowAnnotationExemptions &&
|
||||
hasExemptionAnnotation(test.Resource.ObjectMeta, checkID) {
|
||||
return nil, nil
|
||||
func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
|
||||
reader := bytes.NewReader(rawBytes)
|
||||
check := config.SchemaCheck{}
|
||||
d := yaml.NewYAMLOrJSONDecoder(reader, 4096)
|
||||
for {
|
||||
if err := d.Decode(&check); err != nil {
|
||||
if err == io.EOF {
|
||||
return check, nil
|
||||
}
|
||||
return check, fmt.Errorf("Decoding schema check failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCheck(conf *config.Configuration, checkID, kind string, target config.TargetKind, meta metaV1.Object, containerName string, isInitContainer bool) (*config.SchemaCheck, error) {
|
||||
check, ok := conf.CustomChecks[checkID]
|
||||
if !ok {
|
||||
check, ok = config.BuiltInChecks[checkID]
|
||||
check, ok = builtInChecks[checkID]
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Check %s not found", checkID)
|
||||
}
|
||||
|
||||
containerName := ""
|
||||
if test.Container != nil {
|
||||
containerName = test.Container.Name
|
||||
}
|
||||
if !conf.IsActionable(check.ID, test.Resource.ObjectMeta, containerName) {
|
||||
namespace := meta.GetNamespace()
|
||||
name := meta.GetName()
|
||||
if !conf.IsActionable(check.ID, namespace, name, containerName) {
|
||||
return nil, nil
|
||||
}
|
||||
if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) {
|
||||
if !check.IsActionable(target, kind, isInitContainer) {
|
||||
return nil, nil
|
||||
}
|
||||
checkPtr, err := check.TemplateForResource(test.Resource.Resource.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return checkPtr, nil
|
||||
return &check, nil
|
||||
}
|
||||
|
||||
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.ValError) ResultMessage {
|
||||
details := []string{}
|
||||
for _, issue := range issues {
|
||||
details = append(details, issue.Message)
|
||||
}
|
||||
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool) ResultMessage {
|
||||
result := ResultMessage{
|
||||
ID: check.ID,
|
||||
Severity: conf.Checks[check.ID],
|
||||
Category: check.Category,
|
||||
Success: passes,
|
||||
// FIXME: need to fix the tests before adding this back
|
||||
//Details: details,
|
||||
}
|
||||
if passes {
|
||||
result.Message = check.SuccessMessage
|
||||
@@ -77,8 +119,8 @@ func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bo
|
||||
const exemptionAnnotationKey = "polaris.fairwinds.com/exempt"
|
||||
const exemptionAnnotationPattern = "polaris.fairwinds.com/%s-exempt"
|
||||
|
||||
func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
|
||||
annot := objMeta.GetAnnotations()
|
||||
func hasExemptionAnnotation(ctrl kube.GenericWorkload, checkID string) bool {
|
||||
annot := ctrl.ObjectMeta.GetAnnotations()
|
||||
val := annot[exemptionAnnotationKey]
|
||||
if strings.ToLower(val) == "true" {
|
||||
return true
|
||||
@@ -91,195 +133,101 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ApplyAllSchemaChecksToResourceProvider applies all available checks to a ResourceProvider
|
||||
func ApplyAllSchemaChecksToResourceProvider(conf *config.Configuration, resourceProvider *kube.ResourceProvider) ([]Result, error) {
|
||||
results := []Result{}
|
||||
for _, resources := range resourceProvider.Resources {
|
||||
kindResults, err := ApplyAllSchemaChecksToAllResources(conf, resourceProvider, resources)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
results = append(results, kindResults...)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ApplyAllSchemaChecksToAllResources applies available checks to a list of resources
|
||||
func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resources []kube.GenericResource) ([]Result, error) {
|
||||
results := []Result{}
|
||||
for _, resource := range resources {
|
||||
result, err := ApplyAllSchemaChecks(conf, resourceProvider, resource)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ApplyAllSchemaChecks applies available checks to a single resource
|
||||
func ApplyAllSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
if resource.PodSpec == nil {
|
||||
return applyNonControllerSchemaChecks(conf, resourceProvider, resource)
|
||||
}
|
||||
return applyControllerSchemaChecks(conf, resourceProvider, resource)
|
||||
}
|
||||
|
||||
func applyNonControllerSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
finalResult := Result{
|
||||
Kind: resource.Kind,
|
||||
Name: resource.ObjectMeta.GetName(),
|
||||
Namespace: resource.ObjectMeta.GetNamespace(),
|
||||
}
|
||||
resultSet, err := applyTopLevelSchemaChecks(conf, resourceProvider, resource, false)
|
||||
finalResult.Results = resultSet
|
||||
return finalResult, err
|
||||
}
|
||||
|
||||
func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
finalResult := Result{
|
||||
Kind: resource.Kind,
|
||||
Name: resource.ObjectMeta.GetName(),
|
||||
Namespace: resource.ObjectMeta.GetNamespace(),
|
||||
}
|
||||
resultSet, err := applyTopLevelSchemaChecks(conf, resourceProvider, resource, true)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
finalResult.Results = resultSet
|
||||
|
||||
podRS, err := applyPodSchemaChecks(conf, resourceProvider, resource)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
podRes := PodResult{
|
||||
Results: podRS,
|
||||
ContainerResults: []ContainerResult{},
|
||||
}
|
||||
finalResult.PodResult = &podRes
|
||||
|
||||
for _, container := range resource.PodSpec.InitContainers {
|
||||
results, err := applyContainerSchemaChecks(conf, resourceProvider, resource, &container, true)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
cRes := ContainerResult{
|
||||
Name: container.Name,
|
||||
Results: results,
|
||||
}
|
||||
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
|
||||
}
|
||||
for _, container := range resource.PodSpec.Containers {
|
||||
results, err := applyContainerSchemaChecks(conf, resourceProvider, resource, &container, false)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
cRes := ContainerResult{
|
||||
Name: container.Name,
|
||||
Results: results,
|
||||
}
|
||||
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
|
||||
}
|
||||
|
||||
return finalResult, nil
|
||||
}
|
||||
|
||||
func applyTopLevelSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, res kube.GenericResource, isController bool) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
ResourceProvider: resources,
|
||||
Resource: res,
|
||||
}
|
||||
if isController {
|
||||
test.Target = config.TargetController
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
Target: config.TargetPod,
|
||||
ResourceProvider: resources,
|
||||
Resource: controller,
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
Target: config.TargetContainer,
|
||||
ResourceProvider: resources,
|
||||
Resource: controller,
|
||||
Container: container,
|
||||
IsInitContianer: isInit,
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
}
|
||||
|
||||
func applySchemaChecks(conf *config.Configuration, test schemaTestCase) (ResultSet, error) {
|
||||
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
result, err := applySchemaCheck(conf, checkID, test)
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
|
||||
continue
|
||||
}
|
||||
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetPod, controller.ObjectMeta, "", false)
|
||||
|
||||
if err != nil {
|
||||
return results, err
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
if result != nil {
|
||||
results[checkID] = *result
|
||||
passes, err := check.CheckPod(&controller.PodSpec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*ResultMessage, error) {
|
||||
check, err := resolveCheck(conf, checkID, test)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var passes bool
|
||||
var issues []jsonschema.ValError
|
||||
if check.SchemaTarget != "" {
|
||||
if check.SchemaTarget == config.TargetPod && check.Target == config.TargetContainer {
|
||||
podCopy := *test.Resource.PodSpec
|
||||
podCopy.InitContainers = []corev1.Container{}
|
||||
podCopy.Containers = []corev1.Container{*test.Container}
|
||||
passes, issues, err = check.CheckPod(&podCopy)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
|
||||
func applyControllerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
|
||||
continue
|
||||
}
|
||||
} else if check.Target == config.TargetPod {
|
||||
passes, issues, err = check.CheckPod(test.Resource.PodSpec)
|
||||
} else if check.Target == config.TargetContainer {
|
||||
passes, issues, err = check.CheckContainer(test.Container)
|
||||
} else {
|
||||
passes, issues, err = check.CheckObject(test.Resource.Resource.Object)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for groupkind := range check.AdditionalValidators {
|
||||
if !passes {
|
||||
break
|
||||
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetController, controller.ObjectMeta, "", false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
resources := test.ResourceProvider.Resources[groupkind]
|
||||
namespace := test.Resource.ObjectMeta.GetNamespace()
|
||||
if test.Resource.Kind == "Namespace" {
|
||||
namespace = test.Resource.ObjectMeta.GetName()
|
||||
}
|
||||
resources = funk.Filter(resources, func(res kube.GenericResource) bool {
|
||||
return res.ObjectMeta.GetNamespace() == namespace
|
||||
}).([]kube.GenericResource)
|
||||
objects := funk.Map(resources, func(res kube.GenericResource) interface{} {
|
||||
return res.Resource.Object
|
||||
}).([]interface{})
|
||||
passes, err = check.CheckAdditionalObjects(groupkind, objects)
|
||||
passes, err := check.CheckController(controller.OriginalObjectJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
}
|
||||
result := makeResult(conf, check, passes, issues)
|
||||
return &result, nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
|
||||
continue
|
||||
}
|
||||
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetContainer, controller.ObjectMeta, container.Name, isInit)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
var passes bool
|
||||
if check.SchemaTarget == config.TargetPod {
|
||||
podCopy := controller.PodSpec
|
||||
podCopy.InitContainers = []corev1.Container{}
|
||||
podCopy.Containers = []corev1.Container{*container}
|
||||
passes, err = check.CheckPod(&podCopy)
|
||||
} else {
|
||||
passes, err = check.CheckContainer(container)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyIngressSchemaChecks(conf *config.Configuration, ingress v1beta1.Ingress) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
check, err := resolveCheck(conf, checkID, ingress.Kind, config.TargetIngress, ingress.ObjectMeta.GetObjectMeta(), "", false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
passes, err := check.CheckObject(ingress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func getSortedKeys(m map[string]config.Severity) []string {
|
||||
|
||||
@@ -144,14 +144,14 @@ func TestValidateResourcesInit(t *testing.T) {
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
var results ResultSet
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, nil, controller, emptyContainer, false)
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, controller, emptyContainer, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Equal(t, uint(1), results.GetSummary().Dangers)
|
||||
assert.Equal(t, uint(1), results.GetSummary().Warnings)
|
||||
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, nil, controller, emptyContainer, true)
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, controller, emptyContainer, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ func (cs CountSummary) GetScore() uint {
|
||||
if total == 0 {
|
||||
return uint(100)
|
||||
}
|
||||
score := uint((float64(cs.Successes*2) / float64(total)) * 100)
|
||||
return score
|
||||
return uint((float64(cs.Successes*2) / float64(total)) * 100)
|
||||
}
|
||||
|
||||
// AddSummary adds two CountSummaries together
|
||||
@@ -125,8 +124,10 @@ func (c Result) GetSummaryByCategory() CountSummaryByCategory {
|
||||
// GetSummary summarizes AuditData
|
||||
func (a AuditData) GetSummary() CountSummary {
|
||||
summary := CountSummary{}
|
||||
for _, res := range a.Results {
|
||||
summary.AddSummary(res.GetSummary())
|
||||
for _, ctrlResult := range a.Results {
|
||||
if ctrlResult.PodResult != nil {
|
||||
summary.AddSummary(ctrlResult.GetSummary())
|
||||
}
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
+49
-28
@@ -16,6 +16,8 @@ package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -54,11 +56,37 @@ func NewWebhook(mgr manager.Manager, validator Validator) {
|
||||
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
|
||||
}
|
||||
|
||||
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, error) {
|
||||
var controller kube.GenericResource
|
||||
// GetObjectFromRawRequest returns the pod object and the controller's object from the raw json bytes.
|
||||
func GetObjectFromRawRequest(raw []byte) (corev1.Pod, interface{}, error) {
|
||||
pod := corev1.Pod{}
|
||||
var originalObject interface{}
|
||||
|
||||
decoded := map[string]interface{}{}
|
||||
err := json.Unmarshal(raw, &decoded)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
podMap := kube.GetPodSpec(decoded)
|
||||
if podMap == nil {
|
||||
return pod, originalObject, errors.New("Object does not contain pods")
|
||||
}
|
||||
encoded, err := json.Marshal(podMap)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
err = json.Unmarshal(encoded, &pod.Spec)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
originalObject = decoded
|
||||
return pod, originalObject, err
|
||||
}
|
||||
|
||||
func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult, error) {
|
||||
pod := corev1.Pod{}
|
||||
var originalObject interface{}
|
||||
var err error
|
||||
if req.AdmissionRequest.Kind.Kind == "Pod" {
|
||||
pod := corev1.Pod{}
|
||||
err := v.decoder.Decode(req, &pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -67,64 +95,57 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.Result, er
|
||||
logrus.Infof("Allowing owned pod %s/%s to pass through webhook", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
|
||||
return nil, nil
|
||||
}
|
||||
controller, err = kube.NewGenericResourceFromPod(pod, pod)
|
||||
originalObject = pod
|
||||
} else {
|
||||
controller, err = kube.NewGenericResourceFromBytes(req.Object.Raw)
|
||||
pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw)
|
||||
}
|
||||
controller, err := kube.NewGenericWorkloadFromPod(pod, originalObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: consider enabling multi-resource checks
|
||||
controllerResult, err := validator.ApplyAllSchemaChecks(&v.Config, nil, controller)
|
||||
controller.Kind = req.AdmissionRequest.Kind.Kind
|
||||
var controllerResult validator.Result
|
||||
controllerResult, err = validator.ValidateController(&v.Config, controller)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &controllerResult, nil
|
||||
return controllerResult.PodResult, nil
|
||||
}
|
||||
|
||||
// Handle for Validator to run validation checks.
|
||||
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
logrus.Info("Starting request")
|
||||
result, err := v.handleInternal(req)
|
||||
podResult, err := v.handleInternal(req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error validating request: %v", err)
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
allowed := true
|
||||
reason := ""
|
||||
if result != nil {
|
||||
numDangers := result.GetSummary().Dangers
|
||||
if podResult != nil {
|
||||
numDangers := podResult.GetSummary().Dangers
|
||||
if numDangers > 0 {
|
||||
allowed = false
|
||||
reason = getFailureReason(*result)
|
||||
reason = getFailureReason(*podResult)
|
||||
}
|
||||
logrus.Infof("%d validation errors found when validating %s", numDangers, result.Name)
|
||||
logrus.Infof("%d validation errors found when validating %s", numDangers, podResult.Name)
|
||||
}
|
||||
return admission.ValidationResponse(allowed, reason)
|
||||
}
|
||||
|
||||
func getFailureReason(result validator.Result) string {
|
||||
func getFailureReason(podResult validator.PodResult) string {
|
||||
reason := "\nPolaris prevented this deployment due to configuration problems:\n"
|
||||
|
||||
for _, message := range result.Results {
|
||||
for _, message := range podResult.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- %s: %s\n", result.Kind, message.Message)
|
||||
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
podResult := result.PodResult
|
||||
if podResult != nil {
|
||||
for _, message := range podResult.Results {
|
||||
for _, containerResult := range podResult.ContainerResults {
|
||||
for _, message := range containerResult.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerResult := range podResult.ContainerResults {
|
||||
for _, message := range containerResult.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
|
||||
}
|
||||
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
resources:
|
||||
limits:
|
||||
memory: 128Mi
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
resources:
|
||||
limits:
|
||||
memory: 256Mi
|
||||
cpu: 100m
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
resources:
|
||||
requests:
|
||||
memory: 256Mi
|
||||
cpu: 100m
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["all"]
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["ALL"]
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- nEt_aDmIn
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_ADMIN
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
@@ -1,11 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers: []
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers: []
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
hostIPC: true
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
hostIPC: false
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user