mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
Compare commits
102
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec1ba2f2db | ||
|
|
39fb27c6dd | ||
|
|
e51b90ef79 | ||
|
|
4de082b443 | ||
|
|
264dc51f4c | ||
|
|
45b1f6a1a5 | ||
|
|
38e540e0cf | ||
|
|
2b17c31957 | ||
|
|
813d9c0a2a | ||
|
|
d7d30ac611 | ||
|
|
8fe7a94260 | ||
|
|
f29571e8bb | ||
|
|
a74714b5e5 | ||
|
|
ba04b7e330 | ||
|
|
d70591b1b8 | ||
|
|
80e6f7214e | ||
|
|
1d20a16cb3 | ||
|
|
37edbc7159 | ||
|
|
d451480acf | ||
|
|
705e6e4016 | ||
|
|
2d9aa54e68 | ||
|
|
4dd3a81bbd | ||
|
|
4b87bafbb9 | ||
|
|
62effa36b6 | ||
|
|
913988e44c | ||
|
|
f281cd1e33 | ||
|
|
073847559a | ||
|
|
9b5438d864 | ||
|
|
be349a885d | ||
|
|
08f2afe6c9 | ||
|
|
9f681b3ed4 | ||
|
|
74cbaefc58 | ||
|
|
bd8d4cc08b | ||
|
|
e66ef8b948 | ||
|
|
7930ede331 | ||
|
|
1625b755ac | ||
|
|
08a7863167 | ||
|
|
8d5247e501 | ||
|
|
d30865cefb | ||
|
|
cb7c10d288 | ||
|
|
bdfcb8fbb8 | ||
|
|
1c1e990454 | ||
|
|
952b6aed65 | ||
|
|
875a8ffd40 | ||
|
|
61e0d34e8b | ||
|
|
8b236c2fa2 | ||
|
|
f504de33cf | ||
|
|
2d33bf2565 | ||
|
|
9824be2c26 | ||
|
|
a81bd29674 | ||
|
|
ac638e01ba | ||
|
|
4a0713c754 | ||
|
|
48fc87a663 | ||
|
|
1ee4df0c81 | ||
|
|
b73aabc42e | ||
|
|
e60793a022 | ||
|
|
c8394bf9d7 | ||
|
|
0b765df04d | ||
|
|
3e96c582f0 | ||
|
|
391b802d4d | ||
|
|
f295dd0fb5 | ||
|
|
97687761a7 | ||
|
|
c856874564 | ||
|
|
09525d01a1 | ||
|
|
10e82cf0ae | ||
|
|
a5144c45b2 | ||
|
|
9be34236cb | ||
|
|
e7eb079921 | ||
|
|
d55cd4bdf8 | ||
|
|
75997f0e93 | ||
|
|
b0d86cdcd2 | ||
|
|
5595de4ef9 | ||
|
|
4ca4c8f0f5 | ||
|
|
4b1d6635e0 | ||
|
|
0a26f3f578 | ||
|
|
85a439653d | ||
|
|
ba63e6691a | ||
|
|
07747d76f2 | ||
|
|
b876d301c8 | ||
|
|
8bfed75498 | ||
|
|
52a5aa8f74 | ||
|
|
8e3cf1ddd9 | ||
|
|
f1bbe9236b | ||
|
|
65c5ff59ca | ||
|
|
166b39b695 | ||
|
|
ef4ea66d49 | ||
|
|
b472f34266 | ||
|
|
1ddd2d985a | ||
|
|
727c6fa2c3 | ||
|
|
f531103fa0 | ||
|
|
d909dfd00b | ||
|
|
6abfbb3570 | ||
|
|
b75db7fc5b | ||
|
|
501744a65f | ||
|
|
9a678b46e2 | ||
|
|
80c433478b | ||
|
|
498ccf2215 | ||
|
|
bf065f9b54 | ||
|
|
2d28ea551a | ||
|
|
71b0e00288 | ||
|
|
a1b63ac417 | ||
|
|
8af4363672 |
+14
-35
@@ -1,7 +1,7 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
rok8s: fairwinds/rok8s-scripts@11
|
||||
rok8s: fairwinds/rok8s-scripts@12
|
||||
oss-docs: fairwinds/oss-docs@0
|
||||
|
||||
executors:
|
||||
@@ -44,20 +44,20 @@ references:
|
||||
sudo apt-get install -yqq jq git
|
||||
|
||||
echo "Installing KIND"
|
||||
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.14.0/kind-linux-amd64
|
||||
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.19.0/kind-linux-amd64
|
||||
chmod 0755 kind-linux-amd64
|
||||
sudo mv kind-linux-amd64 /usr/local/bin/kind
|
||||
kind version
|
||||
|
||||
echo "Installing Kubectl"
|
||||
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.21.12/bin/linux/amd64/kubectl
|
||||
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.25.10/bin/linux/amd64/kubectl
|
||||
chmod 0755 kubectl
|
||||
sudo mv kubectl /usr/local/bin/
|
||||
kubectl version --client
|
||||
|
||||
|
||||
echo "Creating Kubernetes Cluster with Kind"
|
||||
kind create cluster --wait=90s --image kindest/node:v1.21.12
|
||||
kind create cluster --wait=90s --image kindest/node:v1.25.9@sha256:c08d6c52820aa42e533b70bce0c2901183326d86dcdcbedecc9343681db45161
|
||||
docker ps -a
|
||||
|
||||
kubectl version
|
||||
@@ -68,18 +68,17 @@ references:
|
||||
./get_helm.sh
|
||||
|
||||
echo "Installing cert-manager"
|
||||
kubectl create namespace cert-manager
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
helm repo update
|
||||
echo "Helm install"
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.12.1 --set "installCRDs=true" --wait --create-namespace
|
||||
echo "Install cert-manager successful"
|
||||
|
||||
test_binary_dashboard: &test_binary_dashboard
|
||||
run:
|
||||
name: Test Dashboard
|
||||
command: |
|
||||
go run main.go dashboard --port 3000 --audit-path ./examples &
|
||||
go run main.go dashboard --port 3000 --audit-path ./pkg/config/examples &
|
||||
sleep 30
|
||||
curl -f http://localhost:3000 > /dev/null
|
||||
curl -f http://localhost:3000/health > /dev/null
|
||||
@@ -104,10 +103,10 @@ references:
|
||||
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
|
||||
curl -LO https://releases.hashicorp.com/vault/1.13.2/vault_1.13.2_linux_amd64.zip
|
||||
sha256sum vault_1.13.2_linux_amd64.zip | grep f7930279de8381de7c532164b4a4408895d9606c0d24e2e9d2f9acb5dfe99b3c
|
||||
unzip vault_1.13.2_linux_amd64.zip
|
||||
mv vault /usr/bin/vault
|
||||
|
||||
jobs:
|
||||
test_k8s:
|
||||
working_directory: ~/polaris
|
||||
@@ -115,12 +114,13 @@ jobs:
|
||||
executor: vm
|
||||
steps:
|
||||
- checkout
|
||||
- *set_environment_variables
|
||||
- *install_k8s
|
||||
- *test_k8s
|
||||
|
||||
test:
|
||||
docker:
|
||||
- image: cimg/go:1.19
|
||||
- image: cimg/go:1.24.6
|
||||
steps:
|
||||
- checkout
|
||||
- *set_environment_variables
|
||||
@@ -128,16 +128,6 @@ jobs:
|
||||
- run: go test ./... -coverprofile=coverage.txt -covermode=count
|
||||
- *test_binary_dashboard
|
||||
|
||||
insights:
|
||||
docker:
|
||||
- image: quay.io/reactiveops/ci-images:v11.0-stretch
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Insights CI
|
||||
command: curl -L https://insights.fairwinds.com/v0/insights-ci.sh | bash
|
||||
|
||||
build_and_push:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
resource_class: large
|
||||
@@ -146,18 +136,17 @@ jobs:
|
||||
# The goreleaser image tag determins the version of Go.
|
||||
# Manually check goreleaser images for their version of Go.
|
||||
# Ref: https://hub.docker.com/r/goreleaser/goreleaser/tags
|
||||
- image: goreleaser/goreleaser:v1.11.4
|
||||
- image: goreleaser/goreleaser:v2.12.0-nightly
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker:
|
||||
version: 20.10.11
|
||||
- setup_remote_docker
|
||||
- *install_vault_alpine
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/global/env
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/polaris/env
|
||||
- *set_environment_variables
|
||||
- run: docker login quay.io -u="${fairwinds_quay_user}" -p="${fairwinds_quay_token}"
|
||||
- run: docker login quay.io -u="${FAIRWINDS_QUAY_USER}" -p="${FAIRWINDS_QUAY_TOKEN}"
|
||||
- install_goreleaser_dependencies
|
||||
- run: scripts/goreleaser.sh
|
||||
|
||||
@@ -172,16 +161,6 @@ workflows:
|
||||
only: /.*/
|
||||
- build_and_push:
|
||||
context: org-global
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- insights:
|
||||
requires:
|
||||
- build_and_push
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# The action uses an own Dockerfile on purpose because the root Dockerfile takes way too long to build for an action
|
||||
|
||||
FROM alpine:3.10
|
||||
FROM alpine:3.22
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
|
||||
@@ -17,4 +17,4 @@ mkdir polaris
|
||||
tar -xzf $TARGET_FILE -C polaris
|
||||
rm $TARGET_FILE
|
||||
echo "polaris" >> $GITHUB_PATH
|
||||
echo "::set-output name=version::$INPUT_VERSION"
|
||||
echo "version=$INPUT_VERSION" >> $GITHUB_OUTPUT
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
build-int:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup polaris
|
||||
uses: ./.github/actions/setup-polaris
|
||||
with:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
build-ext:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup polaris
|
||||
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
|
||||
with:
|
||||
|
||||
+1
-1
@@ -23,7 +23,6 @@ Tiltfile
|
||||
main
|
||||
.DS_Store
|
||||
|
||||
*-packr.go
|
||||
dist
|
||||
.vscode
|
||||
|
||||
@@ -31,3 +30,4 @@ dist
|
||||
|
||||
node_modules
|
||||
/dist
|
||||
docs/README.md
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
version: 2
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
changelog:
|
||||
@@ -11,7 +12,6 @@ env:
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
- ./scripts/install-and-run-packr2.sh
|
||||
builds:
|
||||
- id: polaris
|
||||
ldflags:
|
||||
@@ -52,11 +52,12 @@ brews:
|
||||
- name: polaris
|
||||
# This is replaced using `envsubst`, depending on the git branch.
|
||||
skip_upload: ${skip_release}
|
||||
tap:
|
||||
repository:
|
||||
owner: FairwindsOps
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
directory: Formula
|
||||
description: Open Source Best Practices for Kubernetes
|
||||
url_template: "https://github.com/FairwindsOps/polaris/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
test: |
|
||||
system "#{bin}/polaris version"
|
||||
dockers:
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
## DO NOT EDIT - Managed by Terraform
|
||||
* @rbren @mggude
|
||||
* @sudermanjr @jdesouza @vitorvezani
|
||||
|
||||
+11
-1
@@ -1,4 +1,14 @@
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.22
|
||||
|
||||
LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \
|
||||
org.opencontainers.image.vendor="FairwindsOps, Inc." \
|
||||
org.opencontainers.image.title="polaris" \
|
||||
org.opencontainers.image.description="Polaris is a cli tool to help discover deprecated apiVersions in Kubernetes" \
|
||||
org.opencontainers.image.documentation="https://polaris.docs.fairwinds.com/" \
|
||||
org.opencontainers.image.source="https://github.com/FairwindsOps/polaris" \
|
||||
org.opencontainers.image.url="https://github.com/FairwindsOps/polaris" \
|
||||
org.opencontainers.image.licenses="Apache License 2.0"
|
||||
|
||||
WORKDIR /usr/local/bin
|
||||
RUN apk -U upgrade
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
@@ -34,13 +34,7 @@ Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwin
|
||||
|
||||
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap,
|
||||
and network with fellow Kubernetes users.
|
||||
[Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)
|
||||
or
|
||||
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
|
||||
|
||||
<a href="https://www.fairwinds.com/t-shirt-offer?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris-tshirt">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png" alt="Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt!" />
|
||||
</a>
|
||||
[Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-2na8gtwb4-DGQ4qgmQbczQyB2NlFlYQQ)
|
||||
|
||||
## Other Projects from Fairwinds
|
||||
|
||||
@@ -55,9 +49,5 @@ Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_
|
||||
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),
|
||||
[Fairwinds Insights](https://fairwinds.com/insights),
|
||||
a platform for auditing and enforcing policy in Kubernetes clusters.
|
||||
|
||||
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_Polaris_Ad.png" alt="Fairwinds Insights" />
|
||||
</a>
|
||||
|
||||
@@ -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 }}"
|
||||
+41
-15
@@ -17,6 +17,7 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -42,9 +43,12 @@ var (
|
||||
resourceToAudit string
|
||||
useColor bool
|
||||
helmChart string
|
||||
helmValues string
|
||||
helmValues []string
|
||||
helmSkipTests bool
|
||||
checks []string
|
||||
auditNamespace string
|
||||
severityLevel string
|
||||
skipSslValidation bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -60,9 +64,12 @@ func init() {
|
||||
auditCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
|
||||
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
|
||||
auditCmd.PersistentFlags().StringVar(&helmChart, "helm-chart", "", "Will fill out Helm template")
|
||||
auditCmd.PersistentFlags().StringVar(&helmValues, "helm-values", "", "Optional flag to add helm values")
|
||||
auditCmd.PersistentFlags().StringSliceVar(&helmValues, "helm-values", []string{}, "Optional flag to add helm values")
|
||||
auditCmd.PersistentFlags().BoolVar(&helmSkipTests, "helm-skip-tests", false, "Corresponds to --skip-tests of helm template")
|
||||
auditCmd.PersistentFlags().StringSliceVar(&checks, "checks", []string{}, "Optional flag to specify specific checks to check")
|
||||
auditCmd.PersistentFlags().StringVar(&auditNamespace, "namespace", "", "Namespace to audit. Only applies to in-cluster audits")
|
||||
auditCmd.PersistentFlags().StringVar(&severityLevel, "severity", "", "Severity level used to filter results. Behaves like log levels. 'danger' is the least verbose (warning, danger)")
|
||||
auditCmd.PersistentFlags().BoolVar(&skipSslValidation, "skip-ssl-validation", false, "Skip https certificate verification")
|
||||
}
|
||||
|
||||
var auditCmd = &cobra.Command{
|
||||
@@ -95,26 +102,27 @@ var auditCmd = &cobra.Command{
|
||||
}
|
||||
if helmChart != "" {
|
||||
var err error
|
||||
auditPath, err = ProcessHelmTemplates(helmChart, helmValues)
|
||||
auditPath, err = ProcessHelmTemplates(helmChart, helmValues, helmSkipTests)
|
||||
if err != nil {
|
||||
logrus.Infof("Couldn't process helm chart: %v", err)
|
||||
logrus.Errorf("Couldn't process helm chart: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
k, err := kube.CreateResourceProvider(context.TODO(), auditPath, resourceToAudit, config)
|
||||
ctx := context.TODO()
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, resourceToAudit, config)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
auditData, err := validator.RunAudit(config, k)
|
||||
auditData, err := validator.RunAudit(context.Background(), 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)
|
||||
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests, severityLevel)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -129,7 +137,7 @@ 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) {
|
||||
func ProcessHelmTemplates(helmChart string, helmValues []string, helmSkipTests bool) (string, error) {
|
||||
cmd := exec.Command("helm", "dependency", "update", helmChart)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -143,12 +151,16 @@ func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
|
||||
}
|
||||
params := []string{
|
||||
"template", helmChart,
|
||||
helmChart,
|
||||
"--generate-name",
|
||||
"--output-dir",
|
||||
dir,
|
||||
}
|
||||
if helmValues != "" {
|
||||
params = append(params, "--values", helmValues)
|
||||
for _, v := range helmValues {
|
||||
params = append(params, "--values", v)
|
||||
}
|
||||
|
||||
if helmSkipTests {
|
||||
params = append(params, "--skip-tests")
|
||||
}
|
||||
|
||||
cmd = exec.Command("helm", params...)
|
||||
@@ -161,10 +173,20 @@ func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFormat string, useColor bool, onlyShowFailedTests bool) {
|
||||
func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFormat string, useColor bool, onlyShowFailedTests bool, severityLevel string) {
|
||||
if onlyShowFailedTests {
|
||||
auditData = auditData.RemoveSuccessfulResults()
|
||||
}
|
||||
|
||||
if severityLevel != "" {
|
||||
switch severityLevel {
|
||||
case "danger":
|
||||
auditData = auditData.FilterResultsBySeverityLevel(cfg.SeverityDanger)
|
||||
case "warning":
|
||||
auditData = auditData.FilterResultsBySeverityLevel(cfg.SeverityWarning)
|
||||
}
|
||||
}
|
||||
|
||||
var outputBytes []byte
|
||||
var err error
|
||||
if outputFormat == "score" {
|
||||
@@ -202,9 +224,13 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
|
||||
} else {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
client := &http.Client{}
|
||||
if skipSslValidation {
|
||||
transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
client = &http.Client{Transport: transport}
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error making request for output: %v", err)
|
||||
os.Exit(1)
|
||||
@@ -223,7 +249,7 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
|
||||
}
|
||||
|
||||
if outputFile != "" {
|
||||
err := os.WriteFile(outputFile, []byte(outputBytes), 0644)
|
||||
err := os.WriteFile(outputFile, outputBytes, 0644)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error writing output to file: %v", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -54,7 +55,11 @@ var dashboardCmd = &cobra.Command{
|
||||
auditData := validator.ReadAuditFromFile(loadAuditFile)
|
||||
auditDataPtr = &auditData
|
||||
}
|
||||
router := dashboard.GetRouter(config, auditPath, serverPort, basePath, auditDataPtr)
|
||||
router, err := dashboard.GetRouter(context.Background(), config, auditPath, serverPort, basePath, auditDataPtr)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating router: %v", err)
|
||||
}
|
||||
|
||||
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
+8
-147
@@ -15,26 +15,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/mutation"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/fairwindsops/polaris/pkg/fix"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const templateLineMarker = "# POLARIS_FIX_TMPL"
|
||||
const templateOpenMarker = "POLARIS_OPEN_TMPL"
|
||||
const templateCloseMarker = "POLARIS_CLOSE_TMPL"
|
||||
|
||||
var (
|
||||
filesPath string
|
||||
checksToFix []string
|
||||
fixAll bool
|
||||
isTemplate bool
|
||||
)
|
||||
|
||||
@@ -52,145 +44,14 @@ var fixCommand = &cobra.Command{
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Debug("Setting up controller manager")
|
||||
|
||||
if filesPath == "" {
|
||||
logrus.Error("Please specify a files-path flag")
|
||||
cmd.Help()
|
||||
os.Exit(1)
|
||||
}
|
||||
var yamlFiles []string
|
||||
fileInfo, err := os.Stat(filesPath)
|
||||
err := fix.Execute(context.Background(), config, filesPath, isTemplate, checksToFix...)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
baseDir := filesPath
|
||||
if !strings.HasSuffix(filesPath, "/") {
|
||||
baseDir = baseDir + "/"
|
||||
}
|
||||
yamlFiles, err = getYamlFiles(baseDir)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
if errors.Is(err, fix.ErrFilesPathRequired) {
|
||||
logrus.Error("Please specify a files-path flag")
|
||||
cmd.Help()
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
yamlFiles = append(yamlFiles, filesPath)
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if len(checksToFix) > 0 {
|
||||
if len(checksToFix) == 1 && checksToFix[0] == "all" {
|
||||
allchecks := []string{}
|
||||
for key := range config.Checks {
|
||||
allchecks = append(allchecks, key)
|
||||
}
|
||||
config.Mutations = allchecks
|
||||
} else if len(checksToFix) == 0 && checksToFix[0] == "none" {
|
||||
config.Mutations = nil
|
||||
} else {
|
||||
config.Mutations = checksToFix
|
||||
}
|
||||
}
|
||||
|
||||
for _, fullFilePath := range yamlFiles {
|
||||
yamlContent, err := os.ReadFile(fullFilePath)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error reading file with file path %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error marshalling %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
if isTemplate {
|
||||
yamlContent = []byte(detemplate(string(yamlContent)))
|
||||
}
|
||||
kubeResources := kube.CreateResourceProviderFromYaml(string(yamlContent))
|
||||
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error applying schema check to the resources %s: %v", fullFilePath, err)
|
||||
}
|
||||
allMutations := mutation.GetMutationsFromResults(results)
|
||||
|
||||
updatedYamlContent := ""
|
||||
if len(allMutations) > 0 {
|
||||
for _, resources := range kubeResources.Resources {
|
||||
for _, resource := range resources {
|
||||
key := fmt.Sprintf("%s/%s/%s", resource.Kind, resource.Resource.GetName(), resource.Resource.GetNamespace())
|
||||
mutations := allMutations[key]
|
||||
mutatedYamlContent, err := mutation.ApplyAllMutations(string(resource.OriginalObjectYAML), mutations)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error applying schema mutations to the resource %s: %v", key, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if updatedYamlContent != "" {
|
||||
updatedYamlContent += "\n---\n"
|
||||
}
|
||||
updatedYamlContent += mutatedYamlContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isTemplate {
|
||||
updatedYamlContent = retemplate(updatedYamlContent)
|
||||
}
|
||||
|
||||
if updatedYamlContent != "" {
|
||||
err = os.WriteFile(fullFilePath, []byte(updatedYamlContent), 0644)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error writing output to file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func detemplate(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
for idx, line := range lines {
|
||||
lines[idx] = detemplateLine(line)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func retemplate(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
for idx, line := range lines {
|
||||
lines[idx] = retemplateLine(line)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func detemplateLine(line string) string {
|
||||
if !strings.HasPrefix(strings.TrimSpace(line), "{{") {
|
||||
line = strings.ReplaceAll(line, "{", templateOpenMarker)
|
||||
line = strings.ReplaceAll(line, "}", templateCloseMarker)
|
||||
return line
|
||||
}
|
||||
tmplStart := strings.Index(line, "{{")
|
||||
newLine := line[:tmplStart] + templateLineMarker + line[tmplStart:]
|
||||
return newLine
|
||||
}
|
||||
|
||||
func retemplateLine(line string) string {
|
||||
if !strings.Contains(line, templateLineMarker) {
|
||||
line = strings.ReplaceAll(line, templateOpenMarker, "{")
|
||||
line = strings.ReplaceAll(line, templateCloseMarker, "}")
|
||||
return line
|
||||
}
|
||||
return strings.Replace(line, templateLineMarker, "", 1)
|
||||
}
|
||||
|
||||
func getYamlFiles(rootpath string) ([]string, error) {
|
||||
var list []string
|
||||
err := filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
|
||||
list = append(list, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return list, err
|
||||
}
|
||||
|
||||
+22
-7
@@ -16,18 +16,26 @@ package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var configPath string
|
||||
var disallowExemptions, disallowConfigExemptions, disallowAnnotationExemptions, fixChecks bool
|
||||
var logLevel string
|
||||
var auditPath string
|
||||
var displayName string
|
||||
var kubeContext string
|
||||
var (
|
||||
mergeConfig bool
|
||||
configPath string
|
||||
disallowExemptions bool
|
||||
disallowConfigExemptions bool
|
||||
disallowAnnotationExemptions bool
|
||||
fixChecks bool
|
||||
logLevel string
|
||||
auditPath string
|
||||
displayName string
|
||||
kubeContext string
|
||||
insightsHost string
|
||||
)
|
||||
|
||||
var (
|
||||
version string
|
||||
@@ -35,12 +43,14 @@ var (
|
||||
|
||||
func init() {
|
||||
// Flags
|
||||
rootCmd.PersistentFlags().BoolVarP(&mergeConfig, "merge-config", "m", false, "If true, custom configuration will be merged with default configuration instead of replacing it.")
|
||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Location of Polaris configuration file.")
|
||||
rootCmd.PersistentFlags().StringVarP(&kubeContext, "context", "x", "", "Set the kube context.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any configured exemption.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowConfigExemptions, "disallow-config-exemptions", "", false, "Disallow exemptions set within the configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowAnnotationExemptions, "disallow-annotation-exemptions", "", false, "Disallow any exemption defined as a controller annotation.")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level to be output (trace, debug, info, warning, error, fatal, panic).")
|
||||
rootCmd.PersistentFlags().StringVar(&insightsHost, "insights-host", "https://insights.fairwinds.com", "Fairwinds Insights host URL")
|
||||
}
|
||||
|
||||
var config conf.Configuration
|
||||
@@ -57,7 +67,7 @@ var rootCmd = &cobra.Command{
|
||||
logrus.SetLevel(parsedLevel)
|
||||
}
|
||||
|
||||
config, err = conf.ParseFile(configPath)
|
||||
config, err = conf.MergeConfigAndParseFile(configPath, mergeConfig)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing config at %s: %v", configPath, err)
|
||||
os.Exit(1)
|
||||
@@ -76,6 +86,11 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
os.Exit(1)
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
if !strings.HasPrefix(cmd.Use, "audit") {
|
||||
os.Stderr.WriteString("\n\nWant more? Automate Polaris for free with Fairwinds Insights!\n🚀 https://fairwinds.com/insights-signup/polaris 🚀 \n")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Execute the stuff
|
||||
|
||||
@@ -31,4 +31,7 @@ var versionCmd = &cobra.Command{
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Polaris version:" + version)
|
||||
},
|
||||
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
+10
-7
@@ -15,6 +15,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
k8sConfig "sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
)
|
||||
|
||||
var webhookPort int
|
||||
@@ -49,8 +51,12 @@ var webhookCmd = &cobra.Command{
|
||||
logrus.Debug("Setting up controller manager")
|
||||
|
||||
mgr, err := manager.New(k8sConfig.GetConfigOrDie(), manager.Options{
|
||||
CertDir: certDir,
|
||||
Port: webhookPort,
|
||||
WebhookServer: webhook.NewServer(webhook.Options{
|
||||
CertDir: certDir,
|
||||
Port: webhookPort,
|
||||
CertName: "tls.crt",
|
||||
KeyName: "tls.key",
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to set up overall controller manager: %v", err)
|
||||
@@ -61,9 +67,6 @@ var webhookCmd = &cobra.Command{
|
||||
if os.IsNotExist(err) {
|
||||
panic("Cert does not exist")
|
||||
}
|
||||
server := mgr.GetWebhookServer()
|
||||
server.CertName = "tls.crt"
|
||||
server.KeyName = "tls.key"
|
||||
|
||||
if !enableMutations && !enableValidations {
|
||||
logrus.Errorf("One of --mutate or --validate must be set to true")
|
||||
@@ -71,10 +74,10 @@ var webhookCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if enableValidations {
|
||||
fwebhook.NewValidateWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
|
||||
fwebhook.NewValidateWebhook(mgr, config)
|
||||
}
|
||||
if enableMutations {
|
||||
fwebhook.NewMutateWebhook(mgr, fwebhook.Mutator{Config: config, Client: mgr.GetClient()})
|
||||
fwebhook.NewMutateWebhook(context.Background(), mgr, config)
|
||||
}
|
||||
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
|
||||
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
|
||||
|
||||
@@ -11,6 +11,16 @@ var sf14gv = 32793;
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(sf14g, s);
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var gtag = document.createElement('script');
|
||||
gtag.src = "https://www.googletagmanager.com/gtag/js?id=G-ZR5M5SRYKY";
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(gtag, s);
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-ZR5M5SRYKY');
|
||||
})();
|
||||
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
@@ -21,32 +31,3 @@ s.parentNode.insertBefore(t,s)}(window,document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', '521127644762074');
|
||||
fbq('track', 'PageView');
|
||||
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-TM95WXQ');
|
||||
|
||||
!function() {
|
||||
var t = window.driftt = window.drift = window.driftt || [];
|
||||
if (!t.init) {
|
||||
if (t.invoked) return void (window.console && console.error && console.error("Drift snippet included twice."));
|
||||
t.invoked = !0, t.methods = [ "identify", "config", "track", "reset", "debug", "show", "ping", "page", "hide", "off", "on" ],
|
||||
t.factory = function(e) {
|
||||
return function() {
|
||||
var n = Array.prototype.slice.call(arguments);
|
||||
return n.unshift(e), t.push(n), t;
|
||||
};
|
||||
}, t.methods.forEach(function(e) {
|
||||
t[e] = t.factory(e);
|
||||
}), t.load = function(t) {
|
||||
var e = 3e5, n = Math.ceil(new Date() / e) * e, o = document.createElement("script");
|
||||
o.type = "text/javascript", o.async = !0, o.crossorigin = "anonymous", o.src = "https://js.driftt.com/include/" + n + "/" + t + ".js";
|
||||
var i = document.getElementsByTagName("script")[0];
|
||||
i.parentNode.insertBefore(o, i);
|
||||
};
|
||||
}
|
||||
}();
|
||||
drift.SNIPPET_VERSION = '0.3.1';
|
||||
drift.load('dp7v3zbc7xhm');
|
||||
|
||||
@@ -53,9 +53,30 @@ This means Polaris will remediate the issue it finds, rather than rejecting
|
||||
the deployment.
|
||||
|
||||
To enable the mutating webhook, add `--set webhook.mutate=true` to your
|
||||
Helm instlallation command.
|
||||
Helm installation command.
|
||||
|
||||
By default, the only mutation enabled is `pullPolicyNotAlways`. If you'd like to
|
||||
The following default checks currently have mutation support enabled:
|
||||
* `hostPIDSet`
|
||||
* `hostNetworkSet`
|
||||
* `hostIPCSet`
|
||||
* `priorityClassNotSet`
|
||||
* `hostPortSet`
|
||||
* `pullPolicyNotAlways`
|
||||
* `deploymentMissingReplicas`
|
||||
* `dangerousCapabilities`
|
||||
* `cpuLimitsMissing`
|
||||
* `memoryLimitsMissing`
|
||||
* `livenessProbeMissing`
|
||||
* `memoryRequestsMissing`
|
||||
* `cpuRequestsMissing`
|
||||
* `runAsPrivileged`
|
||||
* `readinessProbeMissing`
|
||||
* `privilegeEscalationAllowed`
|
||||
* `notReadOnlyRootFilesystem`
|
||||
* `insecureCapabilities`
|
||||
* `runAsRootAllowed`
|
||||
|
||||
If you'd like to
|
||||
enable other mutations, you can set the `webhook.mutations` flag.
|
||||
|
||||
|
||||
|
||||
+95
-1
@@ -5,6 +5,100 @@ meta:
|
||||
content: "Fairwinds Polaris | Changelog"
|
||||
|
||||
---
|
||||
|
||||
## 9.1.1
|
||||
* Fix `hpaMinAvailability` failure message
|
||||
* Fix `missingPodDisruptionBudget` typo
|
||||
* Rewrite `hpaMaxAvailability` check to use go-template
|
||||
|
||||
## 9.1.0
|
||||
* Add HPA `minAvailable` and HPA `maxAvailable` checks
|
||||
* Fix typo for PDB `minAvailable`
|
||||
|
||||
## 9.0.1
|
||||
* Fix comments handling in `addOrReplaceValue` function
|
||||
|
||||
## 9.0.0
|
||||
* Expose issue fixer and mutations in the library
|
||||
* Remove `packr` in favor of `go:embed`
|
||||
|
||||
## 8.5.6
|
||||
* Fix trying to list cluster-level resources
|
||||
|
||||
## 8.5.5
|
||||
* Fix missing PDB check
|
||||
|
||||
## 8.5.4
|
||||
* Fix conditional expressions should be at very top of `additionalSchemaStrings`
|
||||
* Update alpine to 3.19
|
||||
|
||||
## 8.5.3
|
||||
* Add quiet flag to polaris audit CLI command to suppress 'upload to Insights' prompt
|
||||
|
||||
## 8.5.2
|
||||
* Switch to `controller-utils` package to get workloads
|
||||
|
||||
## 8.5.1
|
||||
* Update `topologySpreadConstraint` check
|
||||
|
||||
## 8.5.0
|
||||
* Add helm-skip-tests flag
|
||||
* Update CLI docs
|
||||
* Handle multiple helm-values files
|
||||
|
||||
## 8.4.0
|
||||
* Change kubernetes.io/ label from name to instance
|
||||
|
||||
## 8.3.0
|
||||
* Add option to filter audit results by severity level
|
||||
* Add insights prompt
|
||||
|
||||
## 8.2.4
|
||||
* Fix nil pointer issue with webhook
|
||||
|
||||
## 8.2.3
|
||||
* Add category for `metadataAndNameMismatched`.
|
||||
* Fix category for `priorityClassNotSet`.
|
||||
|
||||
## 8.2.2
|
||||
* Fix webhook server cert dir argument
|
||||
|
||||
## 8.2.1
|
||||
* Fix on Insights integration
|
||||
|
||||
## 8.2.0
|
||||
* Minor fixes for NSA checks
|
||||
|
||||
## 8.1.0
|
||||
* Add `insights-host` global flag to configure Fairwinds Insights host (defaults to `https://insights.fairwinds.com`).
|
||||
* Add new `auth` sub-commands be able to authenticate on Polaris using Fairwinds Insights credentials
|
||||
- `login` - login using Fairwinds Insights credentials via the web interface or provide a token
|
||||
- `logout` - logout from Fairwinds Insights
|
||||
- `status` - show relevant information regarding login state
|
||||
- `token` - prints the token from local storage
|
||||
* Add new `audit` flags to be able to upload Workloads and Polaris results to Fairwinds Insights
|
||||
- `upload-insights` - indicates that the results should be uploaded to Fairwinds Insights. (defaults to `false`)
|
||||
- `cluster-name` - cluster name that the results belongs to. Creates the cluster if it does not exist. (required if `upload-insights` is used)
|
||||
|
||||
|
||||
## 8.0.0
|
||||
* Change default severity from `ignore` to `warning` for `priorityClassNotSet`, `metadataAndNameMismatched`, `missingPodDisruptionBudget`, `automountServiceAccountToken`, `missingNetworkPolicy` checks.
|
||||
* Change default severity from `warning` to `danger` for `sensitiveContainerEnvVar`, `sensitiveConfigmapContent`, `clusterrolePodExecAttach`, `rolePodExecAttach`, `clusterrolebindingPodExecAttach`, `rolebindingClusterRolePodExecAttach`, `rolebindingRolePodExecAttach`,`clusterrolebindingClusterAdmin`,`rolebindingClusterAdminClusterRole`,`rolebindingClusterAdminRole` checks.
|
||||
|
||||
## 7.4.0
|
||||
* Skip https certificate verification (#920)
|
||||
|
||||
## 7.3.0
|
||||
* Add a check for `topologySpreadConstraint` (#879)
|
||||
|
||||
## 7.2.0
|
||||
* Enable new RBAC / sensitive content / Pod exec checks, add `hasPrefix` and `hasSuffix` functions to the GO template, exempt `system:` name prefixes for RBAC checks, sensitive content checks ignore `valueFrom`, (#832)
|
||||
|
||||
## 7.1.0
|
||||
* Let Polaris modify YAML without losing comments/formatting (#821)
|
||||
* Add checks for RBAC allowing exec or attaching to a Pod (#820)
|
||||
* Add `clusterrolebindingClusterAdmin`, `rolebindingClusterAdminRole`, and `rolebindingClusterAdminClusterRole` checks + schema tests (#823)
|
||||
|
||||
## 7.0.2
|
||||
* Fixes for pretty CLI output
|
||||
* Some new checks (disabled by default)
|
||||
@@ -157,7 +251,7 @@ JSON schema (see changes to `./checks/multipleReplicasForDeployment.yaml`)
|
||||
* Docker image now includes the default config
|
||||
|
||||
### Breaking Changes
|
||||
* Breaking changes in both input and output formats. See [Examples](https://github.com/FairwindsOps/polaris/tree/master/examples) for examples of the new formats.
|
||||
* Breaking changes in both input and output formats. See [Examples](https://github.com/FairwindsOps/polaris/tree/master/pkg/config/examples) for examples of the new formats.
|
||||
* removed config-level configuration for checks like max/min memory settings
|
||||
* changed severity `error` to `danger`
|
||||
* Breaking changes to the CLI
|
||||
|
||||
@@ -14,22 +14,58 @@ key | default | description
|
||||
`livenessProbeMissing` | `warning` | Fails when a liveness probe is not configured for a pod.
|
||||
`tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
|
||||
`priorityClassNotSet` | `warning` | Fails when a priorityClassName is not set for a pod.
|
||||
`deploymentMissingReplicas` | `warning` | Fails when there is only one replica for a deployment.
|
||||
`missingPodDisruptionBudget` | `ignore`
|
||||
`missingPodDisruptionBudget` | `warning` | Fails when PDB is missing.
|
||||
`metadataAndInstanceMismatched` | `warning` | Fails when label `app.kubernetes.io/instance` and `metadata.name` mismatch
|
||||
`topologySpreadConstraint` | `warning` | Fails when there is no topology spread constraint on the pod
|
||||
`hpaMaxAvailability` | `warning` | Fails when `maxAvailable` lesser or equal than `minAvailable` (if defined) for a HorizontalPodAutoscaler
|
||||
`hpaMinAvailability` | `warning` | Fails when `minAvailable` (if defined) lesser or equal to one for a HorizontalPodAutoscaler
|
||||
`pdbMinAvailableGreaterThanHPAMinReplicas` | `warning` | Fails when PDB `minAvailable` is greater than HPA `minReplicas`
|
||||
|
||||
## Background
|
||||
|
||||
### Liveness and Readiness Probes
|
||||
Readiness and liveness probes can help maintain the health of applications running inside Kubernetes. By default, Kubernetes only knows whether or not a process is running, not if it's healthy. Properly configured readiness and liveness probes will also be able to ensure the health of an application.
|
||||
|
||||
Readiness probes are designed to ensure that an application has reached a "ready" state. In many cases there is a period of time between when a webserver process starts and when it is ready to receive traffic. A readiness probe can ensure the traffic is not sent to a pod until it is actually ready to receive traffic.
|
||||
|
||||
Liveness probes are designed to ensure that an application stays in a healthy state. When a liveness probe fails, the pod will be restarted.
|
||||
|
||||
### Image Pull Policy
|
||||
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
|
||||
|
||||
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
|
||||
|
||||
### Topology Spread Constraints
|
||||
|
||||
By default, the Kubernetes scheduler uses a bin-packing algorithm to fit as many pods as possible into a cluster. The scheduler prefers a more evenly distributed general node load to app replicas precisely spread across nodes. Therefore, by default, multi-replica is not guaranteed to be spread across multiple availability zones. Kubernetes provides topologySpreadConstraint configuration in order to better ensure pod spread across multiple AZs and/or Hosts.
|
||||
|
||||
Example of a topologySpreadConstraint spreading across zones:
|
||||
|
||||
```
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: demo-basic-demo
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: basic-demo
|
||||
app.kubernetes.io/instance: demo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: basic-demo
|
||||
app.kubernetes.io/instance: demo
|
||||
spec:
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: "topology.kubernetes.io/zone"
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
```
|
||||
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [What's Wrong With The Docker :latest Tag?](https://vsupalov.com/docker-latest-tag/)
|
||||
@@ -37,3 +73,4 @@ Related to that, relying on cached versions of a Docker image can become a secur
|
||||
- [Kubernetes Docs: Configure Liveness and Readiness Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)
|
||||
- [Utilizing Kubernetes Liveness and Readiness Probes to Automatically Recover From Failure](https://medium.com/spire-labs/utilizing-kubernetes-liveness-and-readiness-probes-to-automatically-recover-from-failure-2fe0314f2b2e)
|
||||
- [Kubernetes Liveness and Readiness Probes: How to Avoid Shooting Yourself in the Foot](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/)
|
||||
- [Topology Spread Constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/)
|
||||
|
||||
+15
-2
@@ -11,17 +11,30 @@ for privilege escalation.
|
||||
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`automountServiceAccountToken` | `warning` | Fails when `automountServiceAccountToken` is automounted.
|
||||
`hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
|
||||
`hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
|
||||
`linuxHardening` | `danger` | Fails when neither `AppArmor`, `Seccomp`, `SELinux`, or dropping Linux Capabilities is in use.
|
||||
`notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
|
||||
`privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
|
||||
`runAsRootAllowed` | `warning` | Fails when `securityContext.runAsNonRoot` is not true.
|
||||
`runAsPrivileged` | `danger` | Fails when `securityContext.privileged` is true.
|
||||
`insecureCapabilities` | `warning` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/checks/insecureCapabilities.yaml)
|
||||
`dangerousCapabilities` | `danger` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/checks/dangerousCapabilities.yaml)
|
||||
`insecureCapabilities` | `warning` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/pkg/config/checks/insecureCapabilities.yaml)
|
||||
`dangerousCapabilities` | `danger` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](https://github.com/FairwindsOps/polaris/tree/master/pkg/config/checks/dangerousCapabilities.yaml)
|
||||
`hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
|
||||
`hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
|
||||
`tlsSettingsMissing` | `warning` | Fails when an Ingress lacks TLS settings.
|
||||
`sensitiveContainerEnvVar` | `danger` | Fails when the container sets potentially sensitive environment variables.
|
||||
`sensitiveConfigmapContent` | `danger` | Fails when potentially sensitive content is detected in the ConfigMap keys or values.
|
||||
`missingNetworkPolicy` | `warning`
|
||||
`clusterrolePodExecAttach` | `danger` | Fails when the ClusterRole allows Pods/exec or pods/attach.
|
||||
`rolePodExecAttach` | `danger` | Fails when the Role allows Pods/exec or pods/attach.
|
||||
`clusterrolebindingPodExecAttach` | `danger` | Fails when the ClusterRoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist.
|
||||
`rolebindingRolePodExecAttach` | `danger` | Fails when the RoleBinding references a Role that allows Pods/exec, allows pods/attach, or that does not exist.
|
||||
`rolebindingClusterRolePodExecAttach` | `danger` | Fails when the RoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist.
|
||||
`clusterrolebindingClusterAdmin` | `danger` | Fails when the ClusterRoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions.
|
||||
`rolebindingClusterAdminClusterRole` | `danger` | Fails when the RoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions.
|
||||
`rolebindingClusterAdminRole` | `danger` | Fails when the RoleBinding references a Role with wildcard permissions.
|
||||
|
||||
## Background
|
||||
|
||||
|
||||
+16
-3
@@ -11,12 +11,14 @@ audit
|
||||
Runs a one-time audit.
|
||||
dashboard
|
||||
Runs the webserver for Polaris dashboard.
|
||||
fix
|
||||
Fix Infrastructure as code files.
|
||||
help
|
||||
Prints help, if you give it a command then it will print help for that command. Same as -h
|
||||
version
|
||||
Prints the version of Polaris
|
||||
webhook
|
||||
Runs the webhook webserver
|
||||
Runs the webhook webserver.
|
||||
|
||||
# global flags
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
@@ -25,6 +27,7 @@ webhook
|
||||
--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.
|
||||
--insights-host string Fairwinds Insights host URL. (default "https://insights.fairwinds.com")
|
||||
--log-level string Logrus log level. (default "info")
|
||||
|
||||
# dashboard flags
|
||||
@@ -38,12 +41,13 @@ webhook
|
||||
|
||||
# audit flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
--checks stringArray Optional flag to specify specific checks to check
|
||||
--checks strings Optional flag to specify specific checks to check
|
||||
--color Whether to use color in pretty format. (default true)
|
||||
--display-name string An optional identifier for the audit.
|
||||
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
|
||||
--helm-chart string Will fill out Helm template
|
||||
--helm-values string Optional flag to add helm values
|
||||
--helm-skip-tests bool Corresponds to --skip-tests of helm template
|
||||
-h, --help help for audit
|
||||
--namespace string Namespace to audit. Only applies to in-cluster audits
|
||||
--only-show-failed-tests If specified, audit output will only show failed tests.
|
||||
@@ -52,10 +56,19 @@ webhook
|
||||
--resource string Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.
|
||||
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
|
||||
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
|
||||
--severity string Severity level used to filter results. Behaves like log levels. 'danger' is the least verbose (warning, danger)
|
||||
--skip-ssl-validation Skip https certificate verification
|
||||
|
||||
# fix flags
|
||||
--checks strings Optional flag to specify specific checks to fix eg. checks=hostIPCSet,hostPIDSet and checks=all applies fix to all defined checks mutations
|
||||
--files-path string mutate and fix one or more YAML files in a specified folder
|
||||
-h, --help help for fix
|
||||
--template set to true when modifyng a YAML template, like a Helm chart (experimental)
|
||||
|
||||
|
||||
# webhook flags
|
||||
--disable-webhook-config-installer disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.
|
||||
-h, --help help for webhook
|
||||
-p, --port int Port for the dashboard webserver. (default 9876)
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
@@ -49,8 +49,7 @@ kind create cluster --wait=90s --image kindest/node:v1.15.11 --name polaris-test
|
||||
docker build -t quay.io/fairwinds/polaris:debug . # or use your own registry
|
||||
docker push quay.io/fairwinds/polaris:debug
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
kubectl create ns cert-manager
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
|
||||
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.12.1 --set "installCRDs=true" --wait
|
||||
POLARIS_IMAGE=quay.io/fairwinds/polaris:debug ./test/webhook_test.sh
|
||||
```
|
||||
to avoid the final cleanup for debugging purposes, you can run
|
||||
|
||||
@@ -5,7 +5,7 @@ meta:
|
||||
---
|
||||
# Configuration
|
||||
|
||||
The default Polaris configuration can be [seen here](https://github.com/FairwindsOps/polaris/blob/master/examples/config.yaml).
|
||||
The default Polaris configuration can be [seen here](https://github.com/FairwindsOps/polaris/blob/master/pkg/config/default.yaml).
|
||||
|
||||
You can customize the configuration to do things like:
|
||||
* Turn checks [on and off](checks.md)
|
||||
|
||||
@@ -7,7 +7,7 @@ meta:
|
||||
|
||||
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.
|
||||
in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/pkg/config/checks) for examples.
|
||||
|
||||
If you write a check that could be useful for others, feel free to open a PR to add it in!
|
||||
|
||||
@@ -25,7 +25,7 @@ customChecks:
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
@@ -73,7 +73,7 @@ customChecks:
|
||||
category: Resources
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -120,7 +120,7 @@ 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
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
@@ -193,7 +193,7 @@ controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
@@ -233,7 +233,7 @@ customChecks:
|
||||
foo:
|
||||
jsonSchema: |
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
Generated
+5297
-14465
File diff suppressed because it is too large
Load Diff
@@ -1,95 +1,77 @@
|
||||
module github.com/fairwindsops/polaris
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/gobuffalo/packr/v2 v2.8.3
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/qri-io/jsonschema v0.1.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/thoas/go-funk v0.9.2
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.25.3
|
||||
k8s.io/apimachinery v0.25.3
|
||||
k8s.io/client-go v0.25.3
|
||||
sigs.k8s.io/controller-runtime v0.13.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
github.com/fairwindsops/controller-utils v0.3.4
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0
|
||||
github.com/qri-io/jsonpointer v0.1.1
|
||||
github.com/qri-io/jsonschema v0.2.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/thoas/go-funk v0.9.3
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.33.3
|
||||
k8s.io/apimachinery v0.33.3
|
||||
k8s.io/client-go v0.33.1
|
||||
sigs.k8s.io/controller-runtime v0.21.0
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.10.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gobuffalo/logger v1.0.7 // indirect
|
||||
github.com/gobuffalo/packd v1.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/karrick/godirwalk v1.17.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/markbates/errx v1.1.0 // indirect
|
||||
github.com/markbates/oncer v1.0.0 // indirect
|
||||
github.com/markbates/safe v1.0.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect
|
||||
golang.org/x/net v0.0.0-20221017152216-f25eb7ecb193 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/term v0.0.0-20221017184919-83659145692c // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/samber/lo v1.51.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/component-base v0.25.3 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.33.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a // indirect
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
|
||||
)
|
||||
|
||||
+18
-7
@@ -15,14 +15,13 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"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{
|
||||
@@ -30,10 +29,14 @@ var (
|
||||
"deploymentMissingReplicas",
|
||||
// Pod checks
|
||||
"hostIPCSet",
|
||||
"hostPathSet",
|
||||
"hostProcess",
|
||||
"hostPIDSet",
|
||||
"hostNetworkSet",
|
||||
"automountServiceAccountToken",
|
||||
"topologySpreadConstraint",
|
||||
// Container checks
|
||||
"procMount",
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
"cpuLimitsMissing",
|
||||
@@ -55,7 +58,7 @@ var (
|
||||
// Other checks
|
||||
"tlsSettingsMissing",
|
||||
"pdbDisruptionsIsZero",
|
||||
"metadataAndNameMismatched",
|
||||
"metadataAndInstanceMismatched",
|
||||
"missingPodDisruptionBudget",
|
||||
"missingNetworkPolicy",
|
||||
"sensitiveConfigmapContent",
|
||||
@@ -67,13 +70,21 @@ var (
|
||||
"clusterrolebindingClusterAdmin",
|
||||
"rolebindingClusterAdminClusterRole",
|
||||
"rolebindingClusterAdminRole",
|
||||
"hpaMaxAvailability",
|
||||
"hpaMinAvailability",
|
||||
"pdbMinAvailableGreaterThanHPAMinReplicas",
|
||||
}
|
||||
|
||||
// BuiltInChecks contains the checks that come pre-installed w/ Polaris
|
||||
BuiltInChecks = map[string]SchemaCheck{}
|
||||
|
||||
//go:embed all:checks
|
||||
checksFS embed.FS
|
||||
)
|
||||
|
||||
func init() {
|
||||
schemaBox = packr.New("Schemas", "../../checks")
|
||||
for _, checkID := range checkOrder {
|
||||
contents, err := schemaBox.Find(checkID + ".yaml")
|
||||
contents, err := checksFS.ReadFile(fmt.Sprintf("checks/%s.yaml", checkID))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+3
-4
@@ -3,9 +3,8 @@ failureMessage: The ServiceAccount will be automounted
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required: ["serviceAccountName"]
|
||||
properties:
|
||||
serviceAccountName:
|
||||
type: string
|
||||
@@ -15,12 +14,11 @@ schema:
|
||||
const: true
|
||||
additionalSchemaStrings:
|
||||
ServiceAccount: |
|
||||
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
|
||||
type: object
|
||||
required:
|
||||
- metadata
|
||||
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
|
||||
- automountServiceAccountToken
|
||||
{{ end }}
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
@@ -34,3 +32,4 @@ additionalSchemaStrings:
|
||||
type: boolean
|
||||
const: false
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
+1
-1
@@ -3,7 +3,7 @@ failureMessage: The ClusterRole allows Pods/exec or pods/attach
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRole
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
anyOf:
|
||||
+5
-3
@@ -3,7 +3,7 @@ failureMessage: The ClusterRoleBinding references the default cluster-admin Clus
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
@@ -37,9 +37,10 @@ schemaString: |
|
||||
const: "cluster-admin"
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
{{ if (ne .roleRef.name "view") }}
|
||||
{{ if and (ne .metadata.name "cluster-admin") (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
@@ -86,3 +87,4 @@ additionalSchemaStrings:
|
||||
- "patch"
|
||||
- "delete"
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
+3
-1
@@ -3,7 +3,7 @@ failureMessage: The ClusterRoleBinding references a ClusterRole that allows Pods
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
@@ -37,6 +37,7 @@ additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
{{ if (ne .roleRef.name "view") }}
|
||||
{{ if and (ne .metadata.name "cluster-admin") (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
@@ -80,3 +81,4 @@ additionalSchemaStrings:
|
||||
- const: 'get'
|
||||
- const: 'create'
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -3,7 +3,7 @@ failureMessage: Container should not have dangerous capabilities
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
@@ -28,4 +28,4 @@ schema:
|
||||
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /securityContext/capabilities
|
||||
path: /securityContext/capabilities/add
|
||||
+1
-1
@@ -6,7 +6,7 @@ controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host IPC should not be configured
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
hostIPC:
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host network should not be configured
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
hostNetwork:
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host PID should not be configured
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
hostPID:
|
||||
@@ -0,0 +1,16 @@
|
||||
successMessage: HostPath volumes are not configured
|
||||
failureMessage: HostPath volumes must be forbidden
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
volumes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
hostPath:
|
||||
type: string
|
||||
const: ''
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host port should not be configured
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
properties:
|
||||
@@ -0,0 +1,31 @@
|
||||
successMessage: Privileged access to the host check is valid
|
||||
failureMessage: Privileged access to the host is disallowed
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
windowsOptions:
|
||||
type: object
|
||||
properties:
|
||||
hostProcess:
|
||||
type: boolean
|
||||
const: false
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
windowsOptions:
|
||||
type: object
|
||||
properties:
|
||||
hostProcess:
|
||||
type: boolean
|
||||
const: false
|
||||
@@ -0,0 +1,35 @@
|
||||
successMessage: HPA has a valid max and min replica configuration
|
||||
failureMessage: HPA maxReplicas and minReplicas should be different
|
||||
category: Reliability
|
||||
target: autoscaling/HorizontalPodAutoscaler
|
||||
schemaString: |
|
||||
"$schema": https://json-schema.org/draft/2019-09/schema#
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
minReplicas:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maxReplicas:
|
||||
type: integer
|
||||
minimum: 1
|
||||
required:
|
||||
- maxReplicas
|
||||
{{- if .spec.minReplicas }}
|
||||
if:
|
||||
properties:
|
||||
minReplicas:
|
||||
type: integer
|
||||
maxReplicas:
|
||||
type: integer
|
||||
then:
|
||||
properties:
|
||||
maxReplicas:
|
||||
exclusiveMinimum: {{ .spec.minReplicas }}
|
||||
else:
|
||||
properties:
|
||||
maxReplicas:
|
||||
minimum: 1
|
||||
{{- end }}
|
||||
@@ -0,0 +1,14 @@
|
||||
successMessage: HPA has a valid min replica configuration
|
||||
failureMessage: HPA minReplicas should be 2 or more
|
||||
category: Reliability
|
||||
target: autoscaling/HorizontalPodAutoscaler
|
||||
schema:
|
||||
"$schema": https://json-schema.org/draft/2019-09/schema#
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
minReplicas:
|
||||
type: integer
|
||||
minimum: 2
|
||||
@@ -3,7 +3,7 @@ failureMessage: Container should not have insecure capabilities
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
@@ -3,8 +3,8 @@ FailureMessage: Use one of AppArmor, Seccomp, SELinux, or dropping Linux Capabil
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
podOrContainerSeccompProfile:
|
||||
type: object
|
||||
{{ $podSeccompProfileType := .Polaris.PodSpec.securityContext.seccompProfile.type }}
|
||||
@@ -83,7 +83,7 @@ schemaString: |
|
||||
type: object
|
||||
{{ else }}
|
||||
anyOf:
|
||||
- $ref: "#/definitions/podOrContainerSeccompProfile"
|
||||
- $ref: "#/definitions/podOrContainerSELinuxOptions"
|
||||
- $ref: "#/definitions/containerDropCapabilities"
|
||||
- $ref: "#/$defs/podOrContainerSeccompProfile"
|
||||
- $ref: "#/$defs/podOrContainerSELinuxOptions"
|
||||
- $ref: "#/$defs/containerDropCapabilities"
|
||||
{{ end}}
|
||||
@@ -10,7 +10,7 @@ containers:
|
||||
- initContainer
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- livenessProbe
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -0,0 +1,18 @@
|
||||
successMessage: Label app.kubernetes.io/instance matches metadata.name
|
||||
failureMessage: Label app.kubernetes.io/instance must match metadata.name
|
||||
category: Reliability
|
||||
target: Controller
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["labels"]
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
required: ["app.kubernetes.io/instance"]
|
||||
properties:
|
||||
app.kubernetes.io/instance:
|
||||
const: "{{ .metadata.name }}"
|
||||
@@ -3,7 +3,7 @@ failureMessage: A NetworkPolicy should match pod labels and contain applied egre
|
||||
category: Security
|
||||
target: PodTemplate
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
+16
-6
@@ -4,17 +4,27 @@ category: Reliability
|
||||
target: Controller
|
||||
controllers:
|
||||
include:
|
||||
- Deployment
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
"$schema": https://json-schema.org/draft/2019-09/schema#
|
||||
type: object
|
||||
required: [spec]
|
||||
properties:
|
||||
metadata:
|
||||
spec:
|
||||
type: object
|
||||
required: [template]
|
||||
properties:
|
||||
labels:
|
||||
template:
|
||||
type: object
|
||||
minProperties: 1
|
||||
required: [metadata]
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: [labels]
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
minProperties: 1
|
||||
additionalSchemaStrings:
|
||||
policy/PodDisruptionBudget: |
|
||||
type: object
|
||||
@@ -30,7 +40,7 @@ additionalSchemaStrings:
|
||||
matchLabels:
|
||||
type: object
|
||||
anyOf:
|
||||
{{ range $key, $value := .metadata.labels }}
|
||||
{{ range $key, $value := .spec.template.metadata.labels }}
|
||||
- properties:
|
||||
"{{ $key }}":
|
||||
type: string
|
||||
+5
-5
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
@@ -25,13 +25,13 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
@@ -40,7 +40,7 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/readOnlyRootFilesystem
|
||||
@@ -3,7 +3,7 @@ failureMessage: Voluntary evictions are not possible
|
||||
category: Reliability
|
||||
target: policy/PodDisruptionBudget
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
@@ -0,0 +1,7 @@
|
||||
successMessage: PDB and HPA are correctly configured
|
||||
failureMessage: PDB minAvailable is greater than HPA minReplicas
|
||||
category: Reliability
|
||||
target: Controller
|
||||
controllers:
|
||||
include:
|
||||
- Deployment
|
||||
@@ -1,9 +1,9 @@
|
||||
successMessage: Priority class has been set
|
||||
failureMessage: Priority class should be set
|
||||
category: Security
|
||||
category: Reliability
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- priorityClassName
|
||||
+5
-5
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
@@ -25,13 +25,13 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
@@ -40,7 +40,7 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
|
||||
mutations:
|
||||
- op: add
|
||||
@@ -0,0 +1,19 @@
|
||||
successMessage: The default /proc masks are set up to reduce attack surface, and should be required
|
||||
failureMessage: Proc mount must not be changed from the default
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
procMount:
|
||||
type: string
|
||||
const: Default
|
||||
@@ -3,7 +3,7 @@ failureMessage: Image pull policy should be "Always"
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
required:
|
||||
- imagePullPolicy
|
||||
properties:
|
||||
@@ -10,7 +10,7 @@ containers:
|
||||
- initContainer
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- readinessProbe
|
||||
@@ -3,7 +3,7 @@ failureMessage: The Role allows Pods/exec or pods/attach
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/Role
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
anyOf:
|
||||
+3
-3
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references the default cluster-admin ClusterRole
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a Role.
|
||||
@@ -45,10 +45,10 @@ schemaString: |
|
||||
const: "cluster-admin"
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
{{ if eq .roleRef.kind "ClusterRole" }}
|
||||
{{ if and (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
+1
-1
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a Role with wildcard permissions
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a ClusterRole.
|
||||
+3
-3
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a ClusterRole that allows Pods/exec,
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a Role.
|
||||
@@ -43,10 +43,10 @@ schemaString: |
|
||||
minLength: 1
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/ClusterRole: |
|
||||
type: object
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
{{ if eq .roleRef.kind "ClusterRole" }}
|
||||
{{ if and (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
+4
-4
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a Role that allows Pods/exec, allows
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a ClusterRole.
|
||||
@@ -17,7 +17,7 @@ schemaString: |
|
||||
const: "rbac.authorization.k8s.io"
|
||||
kind:
|
||||
type: string
|
||||
const: "Role"
|
||||
const: "ClusterRole"
|
||||
# Do not alert on default RoleBindings.
|
||||
- required: ["metadata"]
|
||||
properties:
|
||||
@@ -46,10 +46,10 @@ schemaString: |
|
||||
minLength: 1
|
||||
additionalSchemaStrings:
|
||||
rbac.authorization.k8s.io/Role: |
|
||||
type: object
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
{{ if eq .roleRef.kind "Role" }}
|
||||
{{ if and (not (hasPrefix .metadata.name "system:")) (ne .metadata.name "gce:podsecuritypolicy:calico-sa") }}
|
||||
# This schema is validated for all roleBindings, regardless of their roleRef.
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
allOf:
|
||||
- properties:
|
||||
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
properties:
|
||||
@@ -15,13 +15,13 @@ schema:
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/privileged
|
||||
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
@@ -33,13 +33,13 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
# non-root specified at container level
|
||||
- properties:
|
||||
containers:
|
||||
@@ -49,7 +49,7 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/runAsNonRoot
|
||||
+1
-1
@@ -3,7 +3,7 @@ failureMessage: Potentially sensitive content is detected in the ConfigMap keys
|
||||
category: Security
|
||||
target: /ConfigMap
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required: ["metadata"]
|
||||
properties:
|
||||
+4
-2
@@ -3,14 +3,16 @@ failureMessage: The container sets potentially sensitive environment variables
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
env:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
oneOf:
|
||||
anyOf:
|
||||
- not:
|
||||
required: ["value"]
|
||||
- required: ["name", "value"]
|
||||
properties:
|
||||
name:
|
||||
@@ -3,7 +3,7 @@ failureMessage: Image tag should be specified
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
required:
|
||||
- image
|
||||
allOf:
|
||||
@@ -3,7 +3,7 @@ failureMessage: Ingress does not have TLS configured
|
||||
category: Security
|
||||
target: networking.k8s.io/Ingress
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
@@ -0,0 +1,17 @@
|
||||
successMessage: Pod has a valid topology spread constraint
|
||||
failureMessage: Pod should be configured with a valid topology spread constraint
|
||||
category: Reliability
|
||||
target: PodSpec
|
||||
controllers:
|
||||
exclude:
|
||||
- Job
|
||||
- CronJob
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- topologySpreadConstraints
|
||||
properties:
|
||||
topologySpreadConstraints:
|
||||
type: array
|
||||
minItems: 1
|
||||
@@ -0,0 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRequiredFieldsOnBuiltInChecks(t *testing.T) {
|
||||
for _, v := range BuiltInChecks {
|
||||
assert.NotEmpty(t, v.SuccessMessage)
|
||||
assert.NotEmpty(t, v.FailureMessage)
|
||||
assert.NotEmpty(t, v.Category)
|
||||
assert.NotEmpty(t, v.Target)
|
||||
}
|
||||
}
|
||||
+43
-26
@@ -16,6 +16,7 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -23,7 +24,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
@@ -49,38 +49,55 @@ type Exemption struct {
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
var configBox = (*packr.Box)(nil)
|
||||
//go:embed default.yaml
|
||||
var defaultConfig []byte
|
||||
|
||||
func getConfigBox() *packr.Box {
|
||||
if configBox == (*packr.Box)(nil) {
|
||||
configBox = packr.New("Config", "../../examples")
|
||||
}
|
||||
return configBox
|
||||
}
|
||||
|
||||
// ParseFile parses config from a file.
|
||||
func ParseFile(path string) (Configuration, error) {
|
||||
var rawBytes []byte
|
||||
var err error
|
||||
if path == "" {
|
||||
rawBytes, err = getConfigBox().Find("config.yaml")
|
||||
} else if strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://") {
|
||||
// path is a url
|
||||
response, err2 := http.Get(path)
|
||||
if err2 != nil {
|
||||
return Configuration{}, err2
|
||||
}
|
||||
rawBytes, err = io.ReadAll(response.Body)
|
||||
} else {
|
||||
// path is local
|
||||
rawBytes, err = os.ReadFile(path)
|
||||
}
|
||||
// MergeConfigAndParseFile parses config from a file.
|
||||
func MergeConfigAndParseFile(customConfigPath string, mergeConfig bool) (Configuration, error) {
|
||||
rawBytes, err := mergeConfigFile(customConfigPath, mergeConfig)
|
||||
if err != nil {
|
||||
return Configuration{}, err
|
||||
}
|
||||
|
||||
return Parse(rawBytes)
|
||||
}
|
||||
|
||||
func mergeConfigFile(customConfigPath string, mergeConfig bool) ([]byte, error) {
|
||||
if customConfigPath == "" {
|
||||
return defaultConfig, nil
|
||||
}
|
||||
|
||||
var customConfigContent []byte
|
||||
var err error
|
||||
if strings.HasPrefix(customConfigPath, "https://") || strings.HasPrefix(customConfigPath, "http://") {
|
||||
// path is a url
|
||||
response, err := http.Get(customConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customConfigContent, err = io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// path is local
|
||||
customConfigContent, err = os.ReadFile(customConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if mergeConfig {
|
||||
mergedConfig, err := mergeYaml(defaultConfig, customConfigContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mergedConfig, nil
|
||||
}
|
||||
|
||||
return customConfigContent, nil
|
||||
}
|
||||
|
||||
// Parse parses config from a byte array.
|
||||
func Parse(rawBytes []byte) (Configuration, error) {
|
||||
reader := bytes.NewReader(rawBytes)
|
||||
|
||||
@@ -52,7 +52,7 @@ customChecks:
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
@@ -69,7 +69,7 @@ customChecks:
|
||||
target: Container
|
||||
jsonSchema: >
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||
"type": "object",
|
||||
"required": ["securityContext"]
|
||||
}
|
||||
@@ -83,7 +83,7 @@ customChecks:
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
@@ -125,7 +125,7 @@ func TestConfigFromURL(t *testing.T) {
|
||||
}()
|
||||
time.Sleep(time.Second)
|
||||
|
||||
parsedConf, err = ParseFile("http://localhost:8081/exampleURL")
|
||||
parsedConf, err = MergeConfigAndParseFile("http://localhost:8081/exampleURL", false)
|
||||
assert.NoError(t, err, "Expected no error when parsing YAML from URL")
|
||||
if err := srv.Shutdown(context.TODO()); err != nil {
|
||||
panic(err)
|
||||
@@ -136,7 +136,7 @@ func TestConfigFromURL(t *testing.T) {
|
||||
|
||||
func TestConfigNoServerError(t *testing.T) {
|
||||
var err error
|
||||
_, err = ParseFile("http://localhost:8081/exampleURL")
|
||||
_, err = MergeConfigAndParseFile("http://localhost:8081/exampleURL", false)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, regexp.MustCompile("connection refused"), err.Error())
|
||||
}
|
||||
@@ -155,22 +155,22 @@ func TestConfigWithCustomChecks(t *testing.T) {
|
||||
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 := check.CheckObject(context.TODO(), valid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
isValid, _, err = check.CheckObject(context.TODO(), 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, problems, err := parsedConf.CustomChecks["foo"].CheckObject(context.TODO(), 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)
|
||||
isValid, _, err = check.CheckObject(context.TODO(), invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
checks:
|
||||
# reliability
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: ignore
|
||||
priorityClassNotSet: warning
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: warning
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
metadataAndNameMismatched: ignore
|
||||
metadataAndInstanceMismatched: warning
|
||||
pdbDisruptionsIsZero: warning
|
||||
missingPodDisruptionBudget: ignore
|
||||
missingPodDisruptionBudget: warning
|
||||
topologySpreadConstraint: warning
|
||||
hpaMaxAvailability: warning
|
||||
hpaMinAvailability: warning
|
||||
pdbMinAvailableGreaterThanHPAMinReplicas: warning
|
||||
|
||||
# efficiency
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
|
||||
# security
|
||||
automountServiceAccountToken: ignore
|
||||
automountServiceAccountToken: warning
|
||||
hostIPCSet: danger
|
||||
hostPathSet: warning
|
||||
hostProcess: warning
|
||||
hostPIDSet: danger
|
||||
linuxHardening: warning
|
||||
missingNetworkPolicy: ignore
|
||||
missingNetworkPolicy: warning
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
procMount: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
@@ -30,17 +38,16 @@ checks:
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
tlsSettingsMissing: warning
|
||||
# These are initially warning and will later be promoted to danger.
|
||||
sensitiveContainerEnvVar: warning
|
||||
sensitiveConfigmapContent: warning
|
||||
clusterrolePodExecAttach: warning
|
||||
rolePodExecAttach: warning
|
||||
clusterrolebindingPodExecAttach: warning
|
||||
rolebindingClusterRolePodExecAttach: warning
|
||||
rolebindingRolePodExecAttach: warning
|
||||
clusterrolebindingClusterAdmin: warning
|
||||
rolebindingClusterAdminClusterRole: warning
|
||||
rolebindingClusterAdminRole: warning
|
||||
sensitiveContainerEnvVar: danger
|
||||
sensitiveConfigmapContent: danger
|
||||
clusterrolePodExecAttach: danger
|
||||
rolePodExecAttach: danger
|
||||
clusterrolebindingPodExecAttach: danger
|
||||
rolebindingClusterRolePodExecAttach: danger
|
||||
rolebindingRolePodExecAttach: danger
|
||||
clusterrolebindingClusterAdmin: danger
|
||||
rolebindingClusterAdminClusterRole: danger
|
||||
rolebindingClusterAdminRole: danger
|
||||
|
||||
|
||||
mutations:
|
||||
@@ -6,25 +6,38 @@ checks:
|
||||
pullPolicyNotAlways: warning
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
topologySpreadConstraint: warning
|
||||
pdbDisruptionsIsZero: warning
|
||||
missingPodDisruptionBudget: warning
|
||||
metadataAndInstanceMismatched: warning
|
||||
hpaMaxAvailability: warning
|
||||
hpaMinAvailability: warning
|
||||
pdbMinAvailableGreaterThanHPAMinReplicas: warning
|
||||
|
||||
# efficiency
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
|
||||
# security
|
||||
automountServiceAccountToken: warning
|
||||
hostIPCSet: danger
|
||||
hostPathSet: warning
|
||||
hostProcess: warning
|
||||
hostPIDSet: danger
|
||||
linuxHardening: danger
|
||||
missingNetworkPolicy: warning
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
procMount: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
tlsSettingsMissing: warning
|
||||
sensitiveContainerEnvVar: danger
|
||||
sensitiveConfigmapContent: danger
|
||||
clusterrolePodExecAttach: danger
|
||||
@@ -39,7 +52,6 @@ checks:
|
||||
resourceLimits: warning
|
||||
imageRegistry: danger
|
||||
|
||||
|
||||
exemptions:
|
||||
- controllerNames:
|
||||
- my-network-controller
|
||||
@@ -63,7 +75,7 @@ customChecks:
|
||||
category: Resources
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -93,7 +105,7 @@ customChecks:
|
||||
category: Images
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
@@ -0,0 +1,45 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3" // do not change the yaml import
|
||||
)
|
||||
|
||||
func mergeYaml(defaultConfig, overridesConfig []byte) ([]byte, error) {
|
||||
var defaultData, overrideConfig map[string]any
|
||||
|
||||
err := yaml.Unmarshal([]byte(defaultConfig), &defaultData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal([]byte(overridesConfig), &overrideConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mergedData := mergeYAMLMaps(defaultData, overrideConfig)
|
||||
|
||||
mergedConfig, err := yaml.Marshal(mergedData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mergedConfig, nil
|
||||
}
|
||||
|
||||
func mergeYAMLMaps(defaults, overrides map[string]any) map[string]any {
|
||||
for k, v := range overrides {
|
||||
if vMap, ok := v.(map[string]any); ok {
|
||||
// if the key exists in defaults and is a map, recursively merge
|
||||
if mv1, ok := defaults[k].(map[string]any); ok {
|
||||
defaults[k] = mergeYAMLMaps(mv1, vMap)
|
||||
} else {
|
||||
defaults[k] = vMap
|
||||
}
|
||||
} else {
|
||||
// add or overwrite the value in defaults
|
||||
defaults[k] = v
|
||||
}
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var defaults = `
|
||||
checks:
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: warning
|
||||
tagNotSpecified: danger
|
||||
existing:
|
||||
sub:
|
||||
key: value
|
||||
`
|
||||
|
||||
var overrides = `
|
||||
checks:
|
||||
pullPolicyNotAlways: ignore
|
||||
tagNotSpecified: overrides
|
||||
existing:
|
||||
sub:
|
||||
key1: value1
|
||||
new: value
|
||||
new:
|
||||
key: value
|
||||
`
|
||||
|
||||
func TestMergeYaml(t *testing.T) {
|
||||
mergedContent, err := mergeYaml([]byte(defaults), []byte(overrides))
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedYAML := `checks:
|
||||
deploymentMissingReplicas: warning
|
||||
priorityClassNotSet: warning
|
||||
pullPolicyNotAlways: ignore
|
||||
tagNotSpecified: overrides
|
||||
existing:
|
||||
new: value
|
||||
sub:
|
||||
key: value
|
||||
key1: value1
|
||||
new:
|
||||
key: value
|
||||
`
|
||||
|
||||
assert.Equal(t, expectedYAML, string(mergedContent))
|
||||
}
|
||||
+67
-39
@@ -16,6 +16,7 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/qri-io/jsonpointer"
|
||||
"github.com/qri-io/jsonschema"
|
||||
"github.com/thoas/go-funk"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -72,10 +74,10 @@ type SchemaCheck struct {
|
||||
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:"-"`
|
||||
Validator jsonschema.Schema `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:"-"`
|
||||
AdditionalValidators map[string]jsonschema.Schema `yaml:"-" json:"-"`
|
||||
Mutations []Mutation `yaml:"mutations" json:"mutations"`
|
||||
}
|
||||
|
||||
@@ -109,8 +111,9 @@ func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
|
||||
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
|
||||
jsonschema.RegisterKeyword("resourceMinimum", newResourceMinimum)
|
||||
jsonschema.RegisterKeyword("resourceMaximum", newResourceMaximum)
|
||||
jsonschema.LoadDraft2019_09()
|
||||
}
|
||||
|
||||
type includeExcludeList struct {
|
||||
@@ -118,47 +121,68 @@ type includeExcludeList struct {
|
||||
Exclude []string `yaml:"exclude"`
|
||||
}
|
||||
|
||||
func newResourceMinimum() jsonschema.Validator {
|
||||
func newResourceMinimum() jsonschema.Keyword {
|
||||
return new(resourceMinimum)
|
||||
}
|
||||
|
||||
func newResourceMaximum() jsonschema.Validator {
|
||||
func newResourceMaximum() jsonschema.Keyword {
|
||||
return new(resourceMaximum)
|
||||
}
|
||||
|
||||
// Validate checks that a specified quanitity is not less than the minimum
|
||||
func (min resourceMinimum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
|
||||
err := validateRange(path, string(min), data, true)
|
||||
func (min resourceMinimum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
|
||||
err := validateRange(string(min), data, true)
|
||||
if err != nil {
|
||||
errs := currentState.Errs
|
||||
*errs = append(*errs, *err...)
|
||||
currentState.Errs = errs
|
||||
}
|
||||
}
|
||||
func (max resourceMaximum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
|
||||
err := validateRange(string(max), data, false)
|
||||
if err != nil {
|
||||
errs := currentState.Errs
|
||||
*errs = append(*errs, *err...)
|
||||
currentState.Errs = errs
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks that a specified quanitity is not greater than the maximum
|
||||
func (max resourceMaximum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
|
||||
err := validateRange(path, string(max), data, false)
|
||||
if err != nil {
|
||||
*errs = append(*errs, *err...)
|
||||
}
|
||||
func (min resourceMinimum) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema {
|
||||
// Not implemented
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.ValError) {
|
||||
func (min resourceMinimum) Register(uri string, registry *jsonschema.SchemaRegistry) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
func (max resourceMaximum) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema {
|
||||
// Not implemented
|
||||
return nil
|
||||
}
|
||||
func (max resourceMaximum) Register(uri string, registry *jsonschema.SchemaRegistry) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.KeyError) {
|
||||
if resNum, ok := i.(float64); ok {
|
||||
i = fmt.Sprintf("%f", resNum)
|
||||
}
|
||||
resStr, ok := i.(string)
|
||||
if !ok {
|
||||
return resource.Quantity{}, &[]jsonschema.ValError{
|
||||
return resource.Quantity{}, &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("Resource quantity %v is not a string", i)},
|
||||
}
|
||||
}
|
||||
q, err := resource.ParseQuantity(resStr)
|
||||
if err != nil {
|
||||
return resource.Quantity{}, &[]jsonschema.ValError{
|
||||
return resource.Quantity{}, &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("Could not parse resource quantity: %s", resStr)},
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func validateRange(path string, limit interface{}, data interface{}, isMinimum bool) *[]jsonschema.ValError {
|
||||
func validateRange(limit interface{}, data interface{}, isMinimum bool) *[]jsonschema.KeyError {
|
||||
limitQuantity, err := parseQuantity(limit)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -170,14 +194,14 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
|
||||
cmp := limitQuantity.Cmp(actualQuantity)
|
||||
if isMinimum {
|
||||
if cmp == 1 {
|
||||
return &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("%s quantity %v is > %v", path, actualQuantity, limitQuantity)},
|
||||
return &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("quantity %v is > %v", actualQuantity, limitQuantity)},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cmp == -1 {
|
||||
return &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("%s quantity %v is < %v", path, actualQuantity, limitQuantity)},
|
||||
return &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("quantity %v is < %v", actualQuantity, limitQuantity)},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,17 +260,21 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
templated := w.String()
|
||||
if strings.TrimSpace(templated) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if kind == "" {
|
||||
newCheck.SchemaString = w.String()
|
||||
newCheck.SchemaString = templated
|
||||
} else {
|
||||
newCheck.AdditionalSchemaStrings[kind] = w.String()
|
||||
newCheck.AdditionalSchemaStrings[kind] = templated
|
||||
}
|
||||
}
|
||||
|
||||
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
|
||||
newCheck.AdditionalValidators = map[string]jsonschema.Schema{}
|
||||
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
|
||||
val := jsonschema.RootSchema{}
|
||||
val := jsonschema.Schema{}
|
||||
err := UnmarshalYAMLOrJSON([]byte(schemaStr), &val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -261,38 +289,38 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
|
||||
}
|
||||
|
||||
// CheckPodSpec checks a pod spec against the schema
|
||||
func (check SchemaCheck) CheckPodSpec(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(pod)
|
||||
func (check SchemaCheck) CheckPodSpec(ctx context.Context, pod *corev1.PodSpec) (bool, []jsonschema.KeyError, error) {
|
||||
return check.CheckObject(ctx, pod)
|
||||
}
|
||||
|
||||
// CheckPodTemplate checks a pod template against the schema
|
||||
func (check SchemaCheck) CheckPodTemplate(podTemplate interface{}) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(podTemplate)
|
||||
func (check SchemaCheck) CheckPodTemplate(ctx context.Context, podTemplate interface{}) (bool, []jsonschema.KeyError, error) {
|
||||
return check.CheckObject(ctx, podTemplate)
|
||||
}
|
||||
|
||||
// CheckController checks a controler's spec against the schema
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
func (check SchemaCheck) CheckController(ctx context.Context, bytes []byte) (bool, []jsonschema.KeyError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(ctx, bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
}
|
||||
|
||||
// CheckContainer checks a container spec against the schema
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(container)
|
||||
func (check SchemaCheck) CheckContainer(ctx context.Context, container *corev1.Container) (bool, []jsonschema.KeyError, error) {
|
||||
return check.CheckObject(ctx, container)
|
||||
}
|
||||
|
||||
// CheckObject checks arbitrary data against the schema
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
|
||||
func (check SchemaCheck) CheckObject(ctx context.Context, obj interface{}) (bool, []jsonschema.KeyError, error) {
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
errs, err := check.Validator.ValidateBytes(ctx, 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) {
|
||||
func (check SchemaCheck) CheckAdditionalObjects(ctx context.Context, groupkind string, objects []interface{}) (bool, error) {
|
||||
val, ok := check.AdditionalValidators[groupkind]
|
||||
if !ok {
|
||||
return false, errors.New("No validator found for " + groupkind)
|
||||
@@ -302,7 +330,7 @@ func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []inte
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
errs, err := val.ValidateBytes(bytes)
|
||||
errs, err := val.ValidateBytes(ctx, bytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
+20
-28
@@ -16,8 +16,11 @@ package dashboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
@@ -26,7 +29,6 @@ import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
packr "github.com/gobuffalo/packr/v2"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -47,27 +49,12 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
templateBox = (*packr.Box)(nil)
|
||||
assetBox = (*packr.Box)(nil)
|
||||
markdownBox = (*packr.Box)(nil)
|
||||
//go:embed all:templates
|
||||
templatesFS embed.FS
|
||||
//go:embed all:assets
|
||||
assetsFS embed.FS
|
||||
)
|
||||
|
||||
// GetAssetBox returns a binary-friendly set of assets packaged from disk
|
||||
func GetAssetBox() *packr.Box {
|
||||
if assetBox == (*packr.Box)(nil) {
|
||||
assetBox = packr.New("Assets", "assets")
|
||||
}
|
||||
return assetBox
|
||||
}
|
||||
|
||||
// GetTemplateBox returns a binary-friendly set of templates for rendering the dash
|
||||
func GetTemplateBox() *packr.Box {
|
||||
if templateBox == (*packr.Box)(nil) {
|
||||
templateBox = packr.New("Templates", "templates")
|
||||
}
|
||||
return templateBox
|
||||
}
|
||||
|
||||
// templateData is passed to the dashboard HTML template
|
||||
type templateData struct {
|
||||
BasePath string
|
||||
@@ -103,9 +90,8 @@ func GetBaseTemplate(name string) (*template.Template, error) {
|
||||
}
|
||||
|
||||
func parseTemplateFiles(tmpl *template.Template, templateFileNames []string) (*template.Template, error) {
|
||||
templateBox := GetTemplateBox()
|
||||
for _, fname := range templateFileNames {
|
||||
templateFile, err := templateBox.Find(fname)
|
||||
templateFile, err := templatesFS.ReadFile("templates/" + fname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -155,9 +141,15 @@ func stripUnselectedNamespaces(data *validator.AuditData, selectedNamespaces []s
|
||||
}
|
||||
|
||||
// GetRouter returns a mux router serving all routes necessary for the dashboard
|
||||
func GetRouter(c config.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) *mux.Router {
|
||||
func GetRouter(ctx context.Context, c config.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) (*mux.Router, error) {
|
||||
router := mux.NewRouter().PathPrefix(basePath).Subrouter()
|
||||
fileServer := http.FileServer(GetAssetBox())
|
||||
|
||||
assetsSubFS, err := fs.Sub(assetsFS, "assets")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileServer := http.FileServer(http.FS(assetsSubFS))
|
||||
router.PathPrefix("/static/").Handler(http.StripPrefix(path.Join(basePath, "/static/"), fileServer))
|
||||
|
||||
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -165,7 +157,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
})
|
||||
|
||||
router.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||
favicon, err := GetAssetBox().Find("favicon-32x32.png")
|
||||
favicon, err := assetsFS.ReadFile("assets/favicon-32x32.png")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting favicon: %v", err)
|
||||
http.Error(w, "Error getting favicon", http.StatusInternalServerError)
|
||||
@@ -185,7 +177,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(ctx, adjustedConf, k)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -221,7 +213,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
|
||||
logrus.Infof("Running audit")
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(adjustedConf, k)
|
||||
auditData, err = validator.RunAudit(ctx, adjustedConf, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting audit data: %v", err)
|
||||
http.Error(w, "Error running audit", 500)
|
||||
@@ -235,7 +227,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
}
|
||||
|
||||
})
|
||||
return router
|
||||
return router, nil
|
||||
}
|
||||
|
||||
// MainHandler gets template data and renders the dashboard with it.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">
|
||||
<img class="fw-logo" src="static/images/white_logo_fairwinds.svg" alt="Fairwinds" />
|
||||
</a>
|
||||
<div style="color: white;"> Want more? Automate Polaris with <a href="https://www.fairwinds.com/insights-signup/polaris"><strong>Fairwinds Insights</strong></a></div>
|
||||
<div class="right-section p-0 d-flex justify-content-between">
|
||||
<a href="https://github.com/FairwindsOps" target="_blank">
|
||||
<img class="gh-logo" src="static/images/white_icon_github.svg" alt="Github" />
|
||||
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/mutation"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
)
|
||||
|
||||
const templateLineMarker = "# POLARIS_FIX_TMPL"
|
||||
const templateOpenMarker = "POLARIS_OPEN_TMPL"
|
||||
const templateCloseMarker = "POLARIS_CLOSE_TMPL"
|
||||
|
||||
var ErrFilesPathRequired = errors.New("files-path flag is required")
|
||||
|
||||
func Execute(ctx context.Context, config config.Configuration, filesPath string, isTemplate bool, checksToFix ...string) error {
|
||||
if filesPath == "" {
|
||||
return ErrFilesPathRequired
|
||||
}
|
||||
|
||||
var yamlFiles []string
|
||||
fileInfo, err := os.Stat(filesPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting file info: %v", err)
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
baseDir := filesPath
|
||||
if !strings.HasSuffix(filesPath, "/") {
|
||||
baseDir = baseDir + "/"
|
||||
}
|
||||
yamlFiles, err = getYamlFiles(baseDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting yaml files from directory: %v", err)
|
||||
}
|
||||
} else {
|
||||
yamlFiles = append(yamlFiles, filesPath)
|
||||
}
|
||||
|
||||
if len(checksToFix) > 0 {
|
||||
if len(checksToFix) == 1 && checksToFix[0] == "all" {
|
||||
allchecks := []string{}
|
||||
for key := range config.Checks {
|
||||
allchecks = append(allchecks, key)
|
||||
}
|
||||
config.Mutations = allchecks
|
||||
} else if len(checksToFix) == 0 && checksToFix[0] == "none" {
|
||||
config.Mutations = nil
|
||||
} else {
|
||||
config.Mutations = checksToFix
|
||||
}
|
||||
}
|
||||
|
||||
for _, fullFilePath := range yamlFiles {
|
||||
yamlContent, err := os.ReadFile(fullFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading file with file path %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
if isTemplate {
|
||||
yamlContent = []byte(detemplate(string(yamlContent)))
|
||||
}
|
||||
kubeResources, err := kube.CreateResourceProviderFromYaml(string(yamlContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating resource provider from yaml: %v", err)
|
||||
}
|
||||
results, err := validator.ApplyAllSchemaChecksToResourceProvider(ctx, &config, kubeResources)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying schema check to the resources %s: %v", fullFilePath, err)
|
||||
}
|
||||
allMutations := mutation.GetMutationsFromResults(results)
|
||||
|
||||
updatedYamlContent := ""
|
||||
if len(allMutations) > 0 {
|
||||
for _, resources := range kubeResources.Resources {
|
||||
for _, resource := range resources {
|
||||
key := fmt.Sprintf("%s/%s/%s", resource.Kind, resource.Resource.GetName(), resource.Resource.GetNamespace())
|
||||
mutations := allMutations[key]
|
||||
mutatedYamlContent, err := mutation.ApplyAllMutations(string(resource.OriginalObjectYAML), mutations)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying schema mutations to the resource %s: %v", key, err)
|
||||
}
|
||||
if updatedYamlContent != "" {
|
||||
updatedYamlContent += "\n---\n"
|
||||
}
|
||||
updatedYamlContent += mutatedYamlContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isTemplate {
|
||||
updatedYamlContent = retemplate(updatedYamlContent)
|
||||
}
|
||||
|
||||
if updatedYamlContent != "" {
|
||||
err = os.WriteFile(fullFilePath, []byte(updatedYamlContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing output to file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func detemplate(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
for idx, line := range lines {
|
||||
lines[idx] = detemplateLine(line)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func retemplate(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
for idx, line := range lines {
|
||||
lines[idx] = retemplateLine(line)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func detemplateLine(line string) string {
|
||||
if !strings.HasPrefix(strings.TrimSpace(line), "{{") {
|
||||
line = strings.ReplaceAll(line, "{", templateOpenMarker)
|
||||
line = strings.ReplaceAll(line, "}", templateCloseMarker)
|
||||
return line
|
||||
}
|
||||
tmplStart := strings.Index(line, "{{")
|
||||
newLine := line[:tmplStart] + templateLineMarker + line[tmplStart:]
|
||||
return newLine
|
||||
}
|
||||
|
||||
func retemplateLine(line string) string {
|
||||
if !strings.Contains(line, templateLineMarker) {
|
||||
line = strings.ReplaceAll(line, templateOpenMarker, "{")
|
||||
line = strings.ReplaceAll(line, templateCloseMarker, "}")
|
||||
return line
|
||||
}
|
||||
return strings.Replace(line, templateLineMarker, "", 1)
|
||||
}
|
||||
|
||||
func getYamlFiles(rootpath string) ([]string, error) {
|
||||
var list []string
|
||||
err := filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
|
||||
list = append(list, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return list, err
|
||||
}
|
||||
+15
-15
@@ -139,7 +139,7 @@ func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
@@ -150,7 +150,7 @@ func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
return workload, err
|
||||
}
|
||||
|
||||
func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return podWorkload, err
|
||||
@@ -181,7 +181,7 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
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)
|
||||
logrus.Warnf("error caching objects of Kind %s %v", firstOwner.Kind, err)
|
||||
break
|
||||
}
|
||||
abstractObject, ok = objectCache[key]
|
||||
@@ -193,7 +193,7 @@ 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)
|
||||
logrus.Warnf("error retrieving parent metadata %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err)
|
||||
return GenericResource{}, err
|
||||
}
|
||||
podSpec := GetPodSpec(abstractObject.Object)
|
||||
@@ -217,11 +217,11 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
func cacheSingleObject(ctx context.Context, apiVersion, kind, namespace, name string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
func cacheSingleObject(ctx context.Context, apiVersion, kind, namespace, name string, dynamicClient dynamic.Interface, restMapper meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
logrus.Debugf("Caching a single %s", kind)
|
||||
object, err := getObject(ctx, namespace, kind, apiVersion, name, dynamicClient, restMapper)
|
||||
object, err := GetObject(ctx, namespace, kind, apiVersion, name, dynamicClient, restMapper)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving object %s/%s/%s/%s because of error: %v", kind, apiVersion, namespace, name, err)
|
||||
logrus.Warnf("error retrieving object %s/%s/%s/%s because of error: %v", kind, apiVersion, namespace, name, err)
|
||||
return err
|
||||
}
|
||||
key := fmt.Sprintf("%s/%s/%s", object.GetKind(), object.GetNamespace(), object.GetName())
|
||||
@@ -230,18 +230,18 @@ func cacheSingleObject(ctx context.Context, apiVersion, kind, namespace, name st
|
||||
return nil
|
||||
}
|
||||
|
||||
func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamicClient dynamic.Interface, restMapper meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
logrus.Debugf("Caching all %s", kind)
|
||||
fqKind := schema.FromAPIVersionAndKind(apiVersion, kind)
|
||||
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
|
||||
mapping, err := restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving mapping of API %s and Kind %s because of error: %v", apiVersion, kind, err)
|
||||
logrus.Warnf("error retrieving mapping of API %s and Kind %s because of error: %v", apiVersion, kind, err)
|
||||
return err
|
||||
}
|
||||
|
||||
objects, err := (*dynamicClient).Resource(mapping.Resource).Namespace("").List(ctx, kubeAPIMetaV1.ListOptions{})
|
||||
objects, err := dynamicClient.Resource(mapping.Resource).Namespace("").List(ctx, kubeAPIMetaV1.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)
|
||||
logrus.Warnf("error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
|
||||
return err
|
||||
}
|
||||
for idx, object := range objects.Items {
|
||||
@@ -252,13 +252,13 @@ func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamic
|
||||
return nil
|
||||
}
|
||||
|
||||
func getObject(ctx context.Context, namespace, kind, version, name string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper) (*unstructured.Unstructured, error) {
|
||||
func GetObject(ctx context.Context, namespace, kind, version, name string, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) (*unstructured.Unstructured, error) {
|
||||
fqKind := schema.FromAPIVersionAndKind(version, kind)
|
||||
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
|
||||
mapping, err := restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object, err := (*dynamicClient).Resource(mapping.Resource).Namespace(namespace).Get(ctx, name, kubeAPIMetaV1.GetOptions{})
|
||||
object, err := dynamicClient.Resource(mapping.Resource).Namespace(namespace).Get(ctx, name, kubeAPIMetaV1.GetOptions{})
|
||||
return object, err
|
||||
}
|
||||
|
||||
|
||||
+74
-111
@@ -26,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fairwindsops/controller-utils/pkg/controller"
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -33,12 +34,12 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"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"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth" // Required for other auth providers like GKE.
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
)
|
||||
@@ -93,12 +94,16 @@ func (rkm resourceKindMap) GetNumberOfControllers() int {
|
||||
return total
|
||||
}
|
||||
|
||||
var kindRewrites = map[string]string{
|
||||
"Ingress": "networking.k8s.io/Ingress",
|
||||
"PodDisruptionBudget": "policy/PodDisruptionBudget",
|
||||
"HorizontalPodAutoscaler": "autoscaling/HorizontalPodAutoscaler",
|
||||
}
|
||||
|
||||
// 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"
|
||||
if val, ok := kindRewrites[k]; ok {
|
||||
return val
|
||||
}
|
||||
return k
|
||||
}
|
||||
@@ -145,20 +150,13 @@ func CreateResourceProvider(ctx context.Context, directory, workload string, c c
|
||||
|
||||
// CreateResourceProviderFromResource creates a new ResourceProvider that just contains one workload
|
||||
func CreateResourceProviderFromResource(ctx context.Context, workload string) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
return nil, configError
|
||||
}
|
||||
kube, err := kubernetes.NewForConfig(kubeConf)
|
||||
dynamicClient, restMapper, clientSet, _, err := GetKubeClient(ctx, "")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error creating Kubernetes client: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
serverVersion, err := kube.Discovery().ServerVersion()
|
||||
serverVersion, err := clientSet.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Cluster API version: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Error fetching Cluster API version: %w", err)
|
||||
}
|
||||
resources := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Resource", workload)
|
||||
|
||||
@@ -171,28 +169,14 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
|
||||
version := parts[2]
|
||||
name := parts[3]
|
||||
|
||||
dynamicInterface, err := dynamic.NewForConfig(kubeConf)
|
||||
obj, err := GetObject(ctx, namespace, kind, version, name, dynamicClient, restMapper)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error connecting to dynamic interface: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
groupResources, err := restmapper.GetAPIGroupResources(kube.Discovery())
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting API Group resources: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
restMapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
||||
obj, err := getObject(ctx, namespace, kind, version, name, &dynamicInterface, &restMapper)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not find workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Could not find workload %s: %w", workload, err)
|
||||
}
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(*obj, nil)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not parse workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Could not parse workload %s: %w", workload, err)
|
||||
}
|
||||
|
||||
resources.Resources.addResource(workloadObj)
|
||||
return &resources, nil
|
||||
}
|
||||
@@ -222,7 +206,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
}
|
||||
err = resources.addResourcesFromYaml(string(contents))
|
||||
if err != nil {
|
||||
logrus.Warnf("Skipping %s: cannot add resource from YAML: %v", path, err)
|
||||
logrus.Warnf("skipping %s: cannot add resource from YAML: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -235,34 +219,52 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromYaml returns a new ResourceProvider using the yaml
|
||||
func CreateResourceProviderFromYaml(yamlContent string) *ResourceProvider {
|
||||
func CreateResourceProviderFromYaml(yamlContent string) (*ResourceProvider, error) {
|
||||
resources := newResourceProvider("unknown", "Content", "unknown")
|
||||
resources.addResourcesFromYaml(string(yamlContent))
|
||||
return &resources
|
||||
err := resources.addResourcesFromYaml(string(yamlContent))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resources, nil
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromCluster creates a new ResourceProvider using live data from a cluster
|
||||
func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfigWithContext(c.KubeContext)
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
return nil, configError
|
||||
}
|
||||
api, err := kubernetes.NewForConfig(kubeConf)
|
||||
dynamicClient, _, clientSet, clusterHost, err := GetKubeClient(ctx, c.KubeContext)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error creating Kubernetes client: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
dynamicInterface, err := dynamic.NewForConfig(kubeConf)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error connecting to dynamic interface: %v", err)
|
||||
return nil, err
|
||||
return CreateResourceProviderFromAPI(ctx, clientSet, clusterHost, dynamicClient, c)
|
||||
}
|
||||
|
||||
func GetKubeClient(ctx context.Context, kubeContext string) (dynamic.Interface, meta.RESTMapper, kubernetes.Interface, string, error) {
|
||||
var kubeConf *rest.Config
|
||||
var err error
|
||||
if len(kubeContext) > 0 {
|
||||
kubeConf, err = config.GetConfigWithContext(kubeContext)
|
||||
} else {
|
||||
kubeConf, err = config.GetConfig()
|
||||
}
|
||||
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface, c)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", fmt.Errorf("Error fetching KubeConfig: %v", err)
|
||||
}
|
||||
clientSet, err := kubernetes.NewForConfig(kubeConf)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", fmt.Errorf("Error creating Kubernetes client: %v", err)
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(kubeConf)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", fmt.Errorf("Error connecting to dynamic interface: %v", err)
|
||||
}
|
||||
resources, err := restmapper.GetAPIGroupResources(clientSet.Discovery())
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", fmt.Errorf("Error getting API Group resources: %v", err)
|
||||
}
|
||||
return dynamicClient, restmapper.NewDiscoveryRESTMapper(resources), clientSet, kubeConf.Host, nil
|
||||
}
|
||||
|
||||
// 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, c conf.Configuration) (*ResourceProvider, error) {
|
||||
listOpts := metav1.ListOptions{}
|
||||
serverVersion, err := kube.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
@@ -302,13 +304,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
}
|
||||
namespaces = nsList
|
||||
}
|
||||
logrus.Info("Loading pods")
|
||||
pods, err := kube.CoreV1().Pods(c.Namespace).List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Info("Setting up restmapper")
|
||||
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
|
||||
if err != nil {
|
||||
@@ -343,16 +338,20 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
var kubernetesResources []GenericResource
|
||||
for _, kind := range additionalKinds {
|
||||
groupKind := parseGroupKind(maybeTransformKindIntoGroupKind(string(kind)))
|
||||
mapping, err := (restMapper).RESTMapping(groupKind)
|
||||
mapping, err := restMapper.RESTMapping(groupKind)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving mapping of Kind %s because of error: %v", kind, err)
|
||||
logrus.Warnf("error retrieving mapping of Kind %s because of error: %v", kind, err)
|
||||
return nil, err
|
||||
}
|
||||
if c.Namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||
logrus.Infof("Skipping %s because of auditing specific namespace", mapping.GroupVersionKind)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Info("Loading " + kind)
|
||||
objects, err := (*dynamic).Resource(mapping.Resource).Namespace(c.Namespace).List(ctx, metav1.ListOptions{})
|
||||
objects, err := dynamic.Resource(mapping.Resource).Namespace(c.Namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
|
||||
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 {
|
||||
@@ -363,17 +362,24 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
kubernetesResources = append(kubernetesResources, res)
|
||||
}
|
||||
}
|
||||
|
||||
objectCache := map[string]unstructured.Unstructured{}
|
||||
|
||||
logrus.Info("Loading controllers")
|
||||
controllers, err := LoadControllers(ctx, pods.Items, dynamic, &restMapper, objectCache)
|
||||
client := controller.Client{
|
||||
Context: ctx,
|
||||
Dynamic: dynamic,
|
||||
RESTMapper: restMapper,
|
||||
}
|
||||
topControllers, err := client.GetAllTopControllersSummary("")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading controllers from pods: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error while getting all TopControllers: %v", err)
|
||||
}
|
||||
for _, workload := range topControllers {
|
||||
topController := workload.TopController
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(topController, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse workload %v: %w", workload, err)
|
||||
}
|
||||
kubernetesResources = append(kubernetesResources, workloadObj)
|
||||
}
|
||||
// resources loaded from custom checks can also contain controllers and thus would be added twice to the provider
|
||||
kubernetesResources = deduplicateControllers(append(kubernetesResources, controllers...))
|
||||
|
||||
provider.Nodes = nodes.Items
|
||||
provider.Namespaces = namespaces.Items
|
||||
@@ -382,49 +388,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
return &provider, nil
|
||||
}
|
||||
|
||||
// LoadControllers loads a list of controllers from the kubeResources Pods
|
||||
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
|
||||
interfaces := []GenericResource{}
|
||||
deduped := map[string]*corev1.Pod{}
|
||||
for idx, pod := range pods {
|
||||
owners := pod.ObjectMeta.OwnerReferences
|
||||
if len(owners) == 0 {
|
||||
deduped[pod.ObjectMeta.Namespace+"/Pod/"+pod.ObjectMeta.Name] = &pods[idx]
|
||||
continue
|
||||
}
|
||||
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = &pods[idx]
|
||||
}
|
||||
for key, pod := range deduped {
|
||||
logrus.Debugf("Resolving controller from pod %s", key)
|
||||
workload, err := ResolveControllerFromPod(ctx, *pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
interfaces = append(interfaces, workload)
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates.
|
||||
// In cases like CronJobs older children can hang around, so this takes the most recent.
|
||||
func deduplicateControllers(inputResources []GenericResource) []GenericResource {
|
||||
controllerMap := make(map[string]GenericResource)
|
||||
for _, controller := range inputResources {
|
||||
key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName()
|
||||
oldController, ok := controllerMap[key]
|
||||
if !ok || controller.ObjectMeta.GetCreationTimestamp().Time.After(oldController.ObjectMeta.GetCreationTimestamp().Time) {
|
||||
controllerMap[key] = controller
|
||||
}
|
||||
}
|
||||
results := make([]GenericResource, len(controllerMap))
|
||||
idx := 0
|
||||
for _, controller := range controllerMap {
|
||||
results[idx] = controller
|
||||
idx++
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (resources *ResourceProvider) addResourcesFromReader(reader io.Reader) error {
|
||||
contents, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
|
||||
+24
-19
@@ -17,6 +17,7 @@ package kube
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -47,8 +48,8 @@ func TestGetResourcesFromPath(t *testing.T) {
|
||||
namespaceCount[controller.ObjectMeta.GetNamespace()]++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 11, provider.Resources.GetLength())
|
||||
assert.Equal(t, 10, namespaceCount[""])
|
||||
assert.Equal(t, 10, provider.Resources.GetLength())
|
||||
assert.Equal(t, 9, namespaceCount[""])
|
||||
assert.Equal(t, 1, namespaceCount["two"])
|
||||
}
|
||||
|
||||
@@ -64,8 +65,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.Resources["apps/Deployment"]), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Resources["apps/Deployment"][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)
|
||||
@@ -87,8 +88,8 @@ func TestAddResourcesFromReader(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.Resources["apps/Deployment"]), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Resources["apps/Deployment"][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)
|
||||
@@ -146,25 +147,29 @@ func TestGetResourceFromAPI(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, tt.clusterName, &dynamicInterface, tt.config)
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, tt.clusterName, dynamicInterface, tt.config)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want.SourceType, resources.SourceType)
|
||||
assert.Equal(t, tt.want.SourceName, resources.SourceName)
|
||||
assert.IsType(t, tt.want.CreationTime, resources.CreationTime)
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, tt.want.SourceType, resources.SourceType)
|
||||
assert.Equal(t, tt.want.SourceName, resources.SourceName)
|
||||
assert.IsType(t, tt.want.CreationTime, resources.CreationTime)
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
for k, v := range resources.Resources {
|
||||
fmt.Println("cont", k, v)
|
||||
}
|
||||
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
|
||||
|
||||
for _, controllers := range resources.Resources {
|
||||
for _, ctrl := range controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
for _, controllers := range resources.Resources {
|
||||
for _, ctrl := range controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
}
|
||||
}
|
||||
for name, val := range expectedNames {
|
||||
assert.Equal(t, true, val, name)
|
||||
}
|
||||
}
|
||||
for name, val := range expectedNames {
|
||||
assert.Equal(t, true, val, name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: batch/v1beta1
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: test
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment-2
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
app: test
|
||||
template:
|
||||
metadata:
|
||||
name: test
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: busybox
|
||||
command: ["tail"]
|
||||
args: ["-f", "/dev/null"]
|
||||
@@ -6,7 +6,7 @@ metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/dashboard.deployment.yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--
|
||||
# Source: polaris/templates/dashboard.deployment.yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
::::
|
||||
|
||||
@@ -161,7 +161,6 @@ func createPathAndFindNodes(node *yaml.Node, selectors []string, create bool) ([
|
||||
node.Value = ""
|
||||
case yaml.SequenceNode:
|
||||
return nil, errors.Errorf("parent node is array, use /*/ or /0/../%v/ instead of .%v to access its item(s) first", len(node.Content)-1, currentSelector)
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("parent node is of unknown kind %v", node.Kind)
|
||||
}
|
||||
@@ -182,6 +181,9 @@ func addOrReplaceValue(node *yaml.Node, splits []string, value *yaml.Node) error
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.Kind == yaml.ScalarNode {
|
||||
if value.LineComment == "" {
|
||||
value.LineComment = node.LineComment // keep the original comment if override is not provided
|
||||
}
|
||||
// Overwrite an existing scalar value with a new value (whatever kind).
|
||||
*node = *value
|
||||
} else if node.Kind == yaml.MappingNode && value.Kind == yaml.MappingNode {
|
||||
|
||||
+104
-68
@@ -25,14 +25,15 @@ var testCases = []struct {
|
||||
mutated string
|
||||
patch config.Mutation
|
||||
message string
|
||||
}{{
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "Denver",
|
||||
Path: "/pets/0/owners/*/location",
|
||||
},
|
||||
mutated: `pets:
|
||||
}{
|
||||
{
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "Denver",
|
||||
Path: "/pets/0/owners/*/location",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
@@ -43,27 +44,27 @@ var testCases = []struct {
|
||||
location: Denver
|
||||
- name: scooby
|
||||
`,
|
||||
}, {
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "remove",
|
||||
Path: "/pets/0/owners/*/aliases",
|
||||
},
|
||||
mutated: `pets:
|
||||
}, {
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "remove",
|
||||
Path: "/pets/0/owners/*/aliases",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
- name: Bob
|
||||
- name: scooby
|
||||
`,
|
||||
}, {
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "rob",
|
||||
Path: "/pets/0/owners/*/aliases/-",
|
||||
},
|
||||
mutated: `pets:
|
||||
}, {
|
||||
original: oldYaml,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "rob",
|
||||
Path: "/pets/0/owners/*/aliases/-",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
@@ -75,23 +76,23 @@ var testCases = []struct {
|
||||
- rob
|
||||
- name: scooby
|
||||
`,
|
||||
}, {
|
||||
original: `
|
||||
}, {
|
||||
original: `
|
||||
pets:
|
||||
- name: fido
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "Alice",
|
||||
Path: "/pets/0/owners/0/name",
|
||||
},
|
||||
mutated: `pets:
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: "Alice",
|
||||
Path: "/pets/0/owners/0/name",
|
||||
},
|
||||
mutated: `pets:
|
||||
- name: fido
|
||||
owners:
|
||||
- name: Alice
|
||||
`,
|
||||
}, {
|
||||
original: `
|
||||
}, {
|
||||
original: `
|
||||
obj:
|
||||
foo:
|
||||
bar:
|
||||
@@ -99,54 +100,89 @@ obj:
|
||||
- b
|
||||
baz: quux
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: map[string]interface{}{
|
||||
"bar": []string{"c", "d"},
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: map[string]interface{}{
|
||||
"bar": []string{"c", "d"},
|
||||
},
|
||||
Path: "/obj/foo",
|
||||
},
|
||||
Path: "/obj/foo",
|
||||
},
|
||||
mutated: `obj:
|
||||
mutated: `
|
||||
obj:
|
||||
foo:
|
||||
bar:
|
||||
- c
|
||||
- d
|
||||
baz: quux
|
||||
`,
|
||||
}, {
|
||||
original: `
|
||||
foo: bar
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: "baz",
|
||||
Path: "/foo",
|
||||
Comment: "# We set this to baz",
|
||||
},
|
||||
mutated: `
|
||||
foo: baz # We set this to baz
|
||||
`,
|
||||
message: "Expected a comment to appear",
|
||||
}, {
|
||||
original: `
|
||||
foo: bar
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: map[string]interface{}{
|
||||
"baz": "quux",
|
||||
}, {
|
||||
original: `foo: bar`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: "baz",
|
||||
Path: "/foo",
|
||||
Comment: "# We set this to baz",
|
||||
},
|
||||
Path: "/extra",
|
||||
Comment: "# These are extra things",
|
||||
},
|
||||
mutated: `
|
||||
mutated: `foo: baz # We set this to baz`,
|
||||
message: "Expected a comment to appear",
|
||||
}, {
|
||||
original: `foo: bar`,
|
||||
patch: config.Mutation{
|
||||
Op: "add",
|
||||
Value: map[string]interface{}{
|
||||
"baz": "quux",
|
||||
},
|
||||
Path: "/extra",
|
||||
Comment: "# These are extra things",
|
||||
},
|
||||
mutated: `
|
||||
foo: bar
|
||||
extra:
|
||||
# These are extra things
|
||||
baz: quux
|
||||
`,
|
||||
message: "Expected a comment to appear next to an object",
|
||||
}}
|
||||
message: "Expected a comment to appear next to an object",
|
||||
},
|
||||
{
|
||||
original: `
|
||||
foo: bar
|
||||
`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: map[string]interface{}{
|
||||
"baz": "quux",
|
||||
},
|
||||
Path: "/extra",
|
||||
Comment: "# These are extra things",
|
||||
},
|
||||
mutated: `
|
||||
foo: bar
|
||||
extra:
|
||||
# These are extra things
|
||||
baz: quux
|
||||
`,
|
||||
message: "Expected a comment to appear next to an object",
|
||||
}, {
|
||||
original: `foo: bar # we should keep this comment`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: "baz",
|
||||
Path: "/foo",
|
||||
},
|
||||
mutated: `foo: baz # we should keep this comment`,
|
||||
message: "Expected a comment to be kept",
|
||||
}, {
|
||||
original: `foo: bar # we should override this comment`,
|
||||
patch: config.Mutation{
|
||||
Op: "replace",
|
||||
Value: "baz",
|
||||
Path: "/foo",
|
||||
Comment: "override",
|
||||
},
|
||||
mutated: `foo: baz # override`,
|
||||
message: "Expected a comment to overridden",
|
||||
},
|
||||
}
|
||||
|
||||
func TestApplyAllMutations(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
@@ -36,7 +37,7 @@ func TestValidatePDB(t *testing.T) {
|
||||
res, err := kube.NewGenericResourceFromUnstructured(pdb, nil)
|
||||
res.Kind = "PodDisruptionBudget"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
actualResult, err := applyNonControllerSchemaChecks(context.Background(), &c, nil, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -76,7 +77,7 @@ func TestValidateIngress(t *testing.T) {
|
||||
}
|
||||
res.Kind = "Ingress"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
actualResult, err := applyNonControllerSchemaChecks(context.Background(), &c, nil, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -69,7 +70,7 @@ func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourc
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
var results ResultSet
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, nil, workload, container, false)
|
||||
results, err = applyContainerSchemaChecks(context.Background(), &parsedConf, nil, workload, container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -93,7 +94,7 @@ func TestValidateResourcesEmptyConfig(t *testing.T) {
|
||||
Name: "Empty",
|
||||
}
|
||||
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{}, nil, getEmptyWorkload(t, ""), container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{}, nil, getEmptyWorkload(t, ""), container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -190,7 +191,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(context.Background(), &conf.Configuration{Checks: tt.probes}, nil, controller, tt.container, tt.isInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -304,7 +305,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(context.Background(), &conf.Configuration{Checks: tt.image}, nil, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -421,7 +422,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(context.Background(), &conf.Configuration{Checks: tt.networkConf}, nil, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -926,7 +927,7 @@ func TestValidateSecurity(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericResourceFromPod(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(context.Background(), &conf.Configuration{Checks: tt.securityConf}, nil, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1071,7 +1072,7 @@ func TestValidateRunAsRoot(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&config, nil, workload, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &config, nil, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestValidateController(t *testing.T) {
|
||||
}
|
||||
|
||||
var actualResult Result
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, deployment)
|
||||
actualResult, err = applyControllerSchemaChecks(context.Background(), &c, nil, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
Category: "Reliability",
|
||||
}
|
||||
for _, controller := range res.Resources["Deployment"] {
|
||||
actualResult, err := applyControllerSchemaChecks(&c, nil, controller)
|
||||
actualResult, err := applyControllerSchemaChecks(context.Background(), &c, nil, controller)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
|
||||
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, 10, res.Resources.GetLength())
|
||||
testResources(res)
|
||||
|
||||
replicaSpec := map[string]interface{}{"replicas": 2}
|
||||
@@ -109,7 +109,7 @@ 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, conf.Configuration{})
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, 2, res.Resources.GetLength(), "Should have two controllers")
|
||||
testResources(res)
|
||||
@@ -137,7 +137,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 = applyControllerSchemaChecks(context.Background(), &c, nil, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, job)
|
||||
actualResult, err = applyControllerSchemaChecks(context.Background(), &c, nil, job)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, cronjob)
|
||||
actualResult, err = applyControllerSchemaChecks(context.Background(), &c, nil, cronjob)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
resources := []kube.GenericResource{workload}
|
||||
|
||||
var actualResults []Result
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
c.Exemptions = []conf.Exemption{{
|
||||
Namespace: "foo",
|
||||
}}
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -233,7 +233,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
resources[0].ObjectMeta.SetAnnotations(map[string]string{
|
||||
exemptionAnnotationKey: "true",
|
||||
})
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
|
||||
|
||||
c.DisallowExemptions = true
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/qri-io/jsonschema"
|
||||
)
|
||||
|
||||
type validatorFunction func(test schemaTestCase) (bool, []jsonschema.KeyError, error)
|
||||
|
||||
var validatorMapper = map[string]validatorFunction{}
|
||||
var lock = &sync.Mutex{}
|
||||
|
||||
func registerCustomChecks(name string, check validatorFunction) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
validatorMapper[name] = check
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user