Compare commits

..
2 Commits
Author SHA1 Message Date
Robert Brennan ad197f34c2 Merge branch 'master' into rb/cos 2021-04-07 12:22:13 -04:00
Robert Brennan 68ca5a8d13 Update CODEOWNERS 2021-04-07 11:55:41 -04:00
116 changed files with 895 additions and 2352 deletions
+14 -4
View File
@@ -17,7 +17,6 @@ references:
echo 'export PUSH_ALL_VERSION_TAGS=true' >> ${BASH_ENV}
echo 'export GOPROXY=https://proxy.golang.org' >> ${BASH_ENV}
echo 'export GO111MODULE=on' >> ${BASH_ENV}
echo 'export GOFLAGS=-mod=mod' >> ${BASH_ENV}
install_k8s: &install_k8s
run:
@@ -59,6 +58,16 @@ references:
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version 0.16.1 --set "installCRDs=true" --wait
echo "Install cert-manager successful"
# Test scripts
update_coverage: &update_coverage
run:
name: Update Coverage
command: |
if [[ -z $CIRCLE_PR_NUMBER ]]; then
bash <(curl -s https://codecov.io/bash)
else
echo "Skipping coverage for forked PR"
fi
test_binary_dashboard: &test_binary_dashboard
run:
name: Test Dashboard
@@ -148,15 +157,16 @@ jobs:
test:
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: circleci/golang:1.16
- image: circleci/golang:1.13
steps:
- checkout
- *set_environment_variables
- run: go get -u golang.org/x/lint/golint
- run: go list ./... | grep -v vendor | xargs golint -set_exit_status
- run: go list ./... | grep -v vendor | xargs go vet
- run: go test ./... -coverprofile=coverage.txt -covermode=count
- run: go test ./pkg/... -coverprofile=coverage.txt -covermode=count
- run: go run main.go audit --audit-path ./deploy --set-exit-code-below-score 100 --set-exit-code-on-danger
- *update_coverage
- *test_binary_dashboard
insights:
@@ -177,7 +187,7 @@ jobs:
release_binary:
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: circleci/golang:1.16
- image: circleci/golang:1.13
steps:
- checkout
- setup_remote_docker
-44
View File
@@ -1,44 +0,0 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
time: "11:00"
ignore:
- dependency-name: cloud.google.com/go
versions:
- ">= 0.57.a, < 0.58"
- dependency-name: github.com/go-logr/logr
versions:
- ">= 0.2.a, < 0.3"
- dependency-name: github.com/go-logr/zapr
versions:
- ">= 0.2.a, < 0.3"
- dependency-name: github.com/googleapis/gnostic
versions:
- ">= 0.4.a, < 0.5"
- dependency-name: github.com/googleapis/gnostic
versions:
- ">= 0.5.a, < 0.6"
- dependency-name: github.com/qri-io/jsonschema
versions:
- ">= 0.2.a, < 0.3"
- dependency-name: k8s.io/api
versions:
- ">= 0.19.a, < 0.20"
- dependency-name: k8s.io/apimachinery
versions:
- ">= 0.19.a, < 0.20"
- dependency-name: k8s.io/client-go
versions:
- ">= 0.19.a, < 0.20"
- dependency-name: sigs.k8s.io/controller-runtime
versions:
- ">= 0.5.a, < 0.6"
- dependency-name: sigs.k8s.io/controller-runtime
versions:
- ">= 0.8.a, < 0.9"
- dependency-name: k8s.io/apimachinery
versions:
- 0.20.4
-18
View File
@@ -1,18 +0,0 @@
daysUntilStale: 30
daysUntilClose: 7
onlyLabels: []
exemptLabels:
- pinned
- security
exemptProjects: false
exemptMilestones: true
exemptAssignees: false
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
limitPerRun: 30
+2 -5
View File
@@ -7,7 +7,7 @@ changelog:
- '^docs:'
- '^test:'
builds:
- id: polaris
- id: watcher
env:
- CGO_ENABLED=0
- GO111MODULE=on
@@ -15,13 +15,10 @@ builds:
- amd64
- arm
- arm64
- 386
goarm:
- 6
- 7
archives:
- id: polaris
builds: ["polaris"]
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
brews:
- name: polaris
github:
+1 -1
View File
@@ -1 +1 @@
* @rbren @makoscafee @baderbuddy
* @rbren @makoscafee @baderbuddy @shaswa
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.16 AS build-env
FROM golang:1.13 AS build-env
WORKDIR /go/src/github.com/fairwindsops/polaris/
ENV GO111MODULE=on
+34 -23
View File
@@ -3,12 +3,12 @@
<br>
<h3>Best Practices for Kubernetes Workload Configuration</h3>
<a href="https://github.com/FairwindsOps/polaris">
<img src="https://img.shields.io/static/v1.svg?label=Version&message=4.0.7&color=239922">
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.2.0&color=239922">
</a>
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
</a>
<a href="https://circleci.com/gh/FairwindsOps/polaris">
<a href="https://circleci.com/gh/FairwindsOps/polaris.svg">
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
</a>
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
@@ -29,30 +29,41 @@ Polaris can be run in three different modes:
<img src="https://polaris.docs.fairwinds.com/img/architecture.svg" alt="Polaris Architecture" width="550"/>
</p>
**Want to learn more?** Reach out on [the Slack channel](https://fairwindscommunity.slack.com/messages/polaris) ([request invite](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)), send an email to `opensource@fairwinds.com`, or join us for [office hours on Zoom](https://fairwindscommunity.slack.com/messages/office-hours)
## Documentation
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
## Join the Fairwinds Open Source Community
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap, and network with fellow Kubernetes users. [Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g) or [join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
## Other Projects from Fairwinds
Enjoying Polaris? Check out some of our other projects:
* [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage
* [Pluto](https://github.com/FairwindsOps/Pluto) - Detect Kubernetes resources that have been deprecated or removed in future versions
* [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available
* [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters
## Fairwinds Insights
## Integration with Fairwinds Insights
<p align="center">
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
<img src="https://polaris.docs.fairwinds.com/img/insights-banner.png" alt="Fairwinds Insights" width="550"/>
</a>
<img src="https://polaris.docs.fairwinds.com/img/FW_Insights_Polaris.svg" alt="Fairwinds Insights" width="550"/>
</p>
[Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
is a platform for auditing Kubernetes clusters and enforcing policy. If you'd like to:
* manage Polaris across a fleet of clusters
* track findings over time
* send results to services like Slack and Datadog
* add additional checks from tools like
[Trivy](https://github.com/aquasecurity/trivy),
[Goldilocks](https://github.com/FairwindsOps/goldilocks/), and
[OPA](https://www.openpolicyagent.org)
you can sign up for a [free account here](https://insights.fairwinds.com?source=polaris).
## Contributing
PRs welcome! Check out the [Contributing Guidelines](https://polaris.docs.fairwinds.com/contributing) and [Code of Conduct](https://polaris.docs.fairwinds.com/code-of-conduct) for more information.
## Further Information
A history of changes to this project can be viewed in the [Changelog](https://polaris.docs.fairwinds.com/changelog)
If you'd like to learn more about Polaris, or if you'd like to speak with
a Kubernetes expert, you can contact `info@fairwinds.com` or [visit our website](https://fairwinds.com)
---
<p align="center">
<img src="https://polaris.docs.fairwinds.com/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
</p>
If you're interested in running Polaris in multiple clusters,
tracking the results over time, integrating with Slack, Datadog, and Jira,
or unlocking other functionality, check out
[Fairwinds Insights](https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris), a platform for auditing and enforcing policy in Kubernetes clusters.
+9 -10
View File
@@ -14,13 +14,12 @@ schema:
properties:
add:
type: array
allOf:
- not:
contains:
const: ALL
- not:
contains:
const: SYS_ADMIN
- not:
contains:
const: NET_ADMIN
not:
contains:
const: ALL
not:
contains:
const: SYS_ADMIN
not:
contains:
const: NET_ADMIN
+2 -1
View File
@@ -53,4 +53,5 @@ schema:
- contains:
const: KILL
- contains:
const: AUDIT_WRITE
const: AUDIT_WRITE
-17
View File
@@ -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 }}"
-39
View File
@@ -1,39 +0,0 @@
successMessage: A PodDisruptionBudget is attached
failureMessage: Should have a PodDisruptionBudget
category: Reliability
target: Controller
controllers:
include:
- Deployment
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
properties:
labels:
type: object
minProperties: 1
additionalSchemaStrings:
policy/PodDisruptionBudget: |
type: object
properties:
spec:
type: object
required: ["selector"]
properties:
selector:
type: object
required: ["matchLabels"]
properties:
matchLabels:
type: object
anyOf:
{{ range $key, $value := .metadata.labels }}
- properties:
"{{ $key }}":
type: string
const: {{ $value }}
required: ["{{ $key }}"]
{{ end }}
+7 -34
View File
@@ -2,42 +2,15 @@ successMessage: Filesystem is read only
failureMessage: Filesystem should be read only
category: Security
target: Container
schemaTarget: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
goodSecurityContext:
type: object
anyOf:
- required:
- readOnlyRootFilesystem
properties:
readOnlyRootFilesystem:
const: true
notBadSecurityContext:
type: object
type: object
required:
- securityContext
properties:
securityContext:
required:
- readOnlyRootFilesystem
properties:
readOnlyRootFilesystem:
const: true
type: object
anyOf:
- required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
- properties:
containers:
type: array
items:
required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
@@ -1,7 +1,7 @@
successMessage: disruptionsAllowed is greater than zero
failureMessage: disruptionsAllowed is not greater than zero
category: Reliability
target: policy/PodDisruptionBudget
target: PodDisruptionBudget
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
+7 -34
View File
@@ -2,42 +2,15 @@ successMessage: Privilege escalation not allowed
failureMessage: Privilege escalation should not be allowed
category: Security
target: Container
schemaTarget: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
goodSecurityContext:
type: object
anyOf:
- required:
- allowPrivilegeEscalation
properties:
allowPrivilegeEscalation:
const: false
notBadSecurityContext:
type: object
type: object
required:
- securityContext
properties:
securityContext:
required:
- allowPrivilegeEscalation
properties:
allowPrivilegeEscalation:
const: false
type: object
anyOf:
- required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
- properties:
containers:
type: array
items:
required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
+3 -14
View File
@@ -2,23 +2,12 @@ successMessage: Not running as privileged
failureMessage: Should not be running as privileged
category: Security
target: Container
schemaTarget: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
notBadSecurityContext:
type: object
type: object
properties:
securityContext:
properties:
privileged:
not:
const: true
type: object
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
+13 -60
View File
@@ -22,8 +22,8 @@ import (
"io/ioutil"
"net/http"
"os"
"os/exec"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
@@ -39,8 +39,6 @@ var auditOutputFile string
var auditOutputFormat string
var resourceToAudit string
var useColor bool
var helmChart string
var helmValues string
func init() {
rootCmd.AddCommand(auditCmd)
@@ -54,8 +52,6 @@ func init() {
auditCmd.PersistentFlags().BoolVar(&useColor, "color", true, "Whether to use color in pretty format.")
auditCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
auditCmd.PersistentFlags().StringVar(&helmChart, "helm-chart", "", "Will fill out Helm template")
auditCmd.PersistentFlags().StringVar(&helmValues, "helm-values", "", "Optional flag to add helm values")
}
var auditCmd = &cobra.Command{
@@ -66,28 +62,8 @@ var auditCmd = &cobra.Command{
if displayName != "" {
config.DisplayName = displayName
}
if helmChart != "" {
var err error
auditPath, err = ProcessHelmTemplates(helmChart, helmValues)
if err != nil {
logrus.Infof("Couldn't process helm chart: %v", err)
os.Exit(1)
}
}
k, err := kube.CreateResourceProvider(context.TODO(), auditPath, resourceToAudit, config)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
os.Exit(1)
}
auditData, err := validator.RunAudit(config, k)
if err != nil {
logrus.Errorf("Error while running audit on resources: %v", err)
os.Exit(1)
}
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests)
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat, useColor)
summary := auditData.GetSummary()
score := summary.GetScore()
@@ -101,50 +77,26 @@ var auditCmd = &cobra.Command{
},
}
// ProcessHelmTemplates turns helm into yaml to be processed by Polaris or the other tools.
func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
cmd := exec.Command("helm", "dependency", "update", helmChart)
output, err := cmd.CombinedOutput()
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string, useColor bool) validator.AuditData {
// Create a kubernetes client resource provider
k, err := kube.CreateResourceProvider(ctx, auditPath, workload, c)
if err != nil {
logrus.Error(string(output))
return "", err
logrus.Errorf("Error fetching Kubernetes resources %v", err)
os.Exit(1)
}
dir, err := ioutil.TempDir("", "*")
if err != nil {
return "", err
}
params := []string{
"template", helmChart,
helmChart,
"--output-dir",
dir,
}
if helmValues != "" {
params = append(params, "--values", helmValues)
}
cmd = exec.Command("helm", params...)
output, err = cmd.CombinedOutput()
var auditData validator.AuditData
auditData, err = validator.RunAudit(c, k, onlyShowFailedTests)
if err != nil {
logrus.Error(string(output))
return "", err
logrus.Errorf("Error while running audit on resources: %v", err)
os.Exit(1)
}
return dir, nil
}
func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFormat string, useColor bool, onlyShowFailedTests bool) {
if onlyShowFailedTests {
auditData = auditData.RemoveSuccessfulResults()
}
var outputBytes []byte
var err error
if outputFormat == "score" {
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
} else if outputFormat == "yaml" {
var jsonBytes []byte
jsonBytes, err = json.Marshal(auditData)
jsonBytes, err := json.Marshal(auditData)
if err == nil {
outputBytes, err = yaml.JSONToYAML(jsonBytes)
}
@@ -203,4 +155,5 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
}
}
}
return auditData
}
+2
View File
@@ -0,0 +1,2 @@
coverage:
range: 50...80
+1 -11
View File
@@ -31,14 +31,6 @@ rules:
verbs:
- 'get'
- 'list'
- apiGroups:
- 'monitoring.coreos.com'
resources:
- 'prometheuses'
- 'alertmanagers'
verbs:
- 'get'
- 'list'
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
@@ -117,9 +109,7 @@ spec:
- command:
- polaris
- dashboard
- --port
- "8080"
image: 'quay.io/fairwinds/polaris:4.0'
image: 'quay.io/fairwinds/polaris:3.1'
imagePullPolicy: 'Always'
name: dashboard
ports:
+1 -9
View File
@@ -31,14 +31,6 @@ rules:
verbs:
- 'get'
- 'list'
- apiGroups:
- 'monitoring.coreos.com'
resources:
- 'prometheuses'
- 'alertmanagers'
verbs:
- 'get'
- 'list'
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
@@ -117,7 +109,7 @@ spec:
command:
- polaris
- webhook
image: 'quay.io/fairwinds/polaris:4.0'
image: 'quay.io/fairwinds/polaris:3.1'
imagePullPolicy: 'Always'
ports:
- containerPort: 9876
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

+1 -1
View File
@@ -28,7 +28,7 @@ kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/downloa
### Helm
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace \
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris \
--set webhook.enable=true --set dashboard.enable=false
```
-32
View File
@@ -1,38 +1,6 @@
---
sidebarDepth: 0
---
## 4.0.7
* Fix goreleaser format
## 4.0.6
* Change goreleaser format
* Fix `--helm-values` flag
## 4.0.5
* Bugfix for repeated objects on the dashboard
## 4.0.4
* Bugfix for validating webhook and non-pod checks
## 4.0.3
* Fixed bad interaction between `--set-exit-score-below` and `--only-show-failed-tests`
* Dependency updates
* Support for Helm chart scanning
## 4.0.2
* Goreleaser fix
## 4.0.1
* Goreleaser fix
## 4.0.0
* Add support for arbitrary resources, like Ingress or PodDisruptionBudget
* Add support check templating (see docs)
* Add support for multi-resource checks (see docs)
### Breaking Changes
* In custom checks, `jsonSchema` is now `schemaString`
* Check `pdbDisruptionsAllowedGreaterThanZero` is now called `pdbDisruptionsIsZero`
## 3.2.0
* Add `--format=pretty` option for CLI output
-1
View File
@@ -11,7 +11,6 @@ key | default | description
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
`multipleReplicasForDeployment` | `ignore` | Fails when there is only one replica for a deployment.
`missingPodDisruptionBudget` | `ignore`
## Background
+1 -2
View File
@@ -33,8 +33,6 @@ webhook
--color Whether to use color in pretty format. (default true)
--display-name string An optional identifier for the audit.
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
--helm-chart string Will fill out Helm template
--helm-values string Optional flag to add helm values
-h, --help help for audit
--only-show-failed-tests If specified, audit output will only show failed tests.
--output-file string Destination file for audit results.
@@ -43,6 +41,7 @@ webhook
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
# webhook flags
--disable-webhook-config-installer disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.
-h, --help help for webhook
+9 -174
View File
@@ -1,24 +1,16 @@
# Custom Checks
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/).
This is how built-in Polaris checks are defined as well - you can see all the built-in checks
in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/checks) for examples.
If you write a check that could be useful for others, feel free to open a PR to add it in!
## Basic Example
For example, to disallow images from quay.io:
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/). For example,
to disallow images from quay.io:
```yaml
checks:
imageRegistry: warning
customChecks:
imageRegistry:
successMessage: Image comes from allowed registries
failureMessage: Image should not be from disallowed registry
category: Security
target: Container
category: Images
target: Container # target can be "Container" or "Pod"
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
@@ -29,168 +21,6 @@ customChecks:
pattern: ^quay.io
```
## Available Options
All custom checks should go under the `customChecks` field in your Polaris config, keyed by the
check ID. Note that you'll also have to set its severity in the `checks` section of your Polaris config.
* `successMessage` - the message to show when the check succeeds
* `failureMessage` - the message to show when the check fails
* `category` - one of `Security`, `Efficiency`, or `Reliability`
* `target` - specifies the type of resource to check. This can be:
* a group and kind, e.g. `apps/Deployment` or `networking.k8s.io/Ingress`
* `Controller`, to check _any_ resource that contains a pod spec (e.g. Deployments, CronJobs, StatefulSets), as well as naked Pods
* `Pod`, same as `Controller`, but the schema applies to the Pod spec rather than the top-level controller
* `Container` same as `Controller`, but the schema applies to all Container specs rather than the top-level controller
* `controllers` - if `target` is `Controller`, `Pod` or `Container`, you can use this to change which types of controllers are checked
* `controllers.include` - _only_ check these controllers
* `controllers.exclude` - check all controllers except these
* `containers` - if `target` is `Container`, you can use this to decide if `initContainers`, `containers`, or both should be checked
* `containers.exclude` - can be set to a list including `initContainer` or `container`
* `schema` - the JSON Schema to check against, as a YAML object
* `schemaString` - this JSON Schema to check against, as a YAML or JSON string. See [Templating](#templating) below
* Note: only _one_ of `schema` and `schemaString` can be specified.
* `additionalSchemas` - see [Multi-Resource Checks](#multi-resource-checks) below
* `additionalSchemaStrings` - see [Multi-Resource Checks](#multi-resource-checks) below
* Note: only _one_ of `additionalSchemas` and `additionalSchemaStrings` can be specified.
## Checking CPU and Memory
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
strings like `1000m` and `1G`. Here's an example check that memory and CPU falls within a certain range.
```yaml
customChecks:
resourceLimits:
containers:
exclude:
- initContainer
successMessage: Resource limits are within the required range
failureMessage: Resource limits should be within the required range
category: Resources
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- limits
properties:
limits:
type: object
required:
- memory
- cpu
properties:
memory:
type: string
resourceMinimum: 100M
resourceMaximum: 6G
cpu:
type: string
resourceMinimum: 100m
resourceMaximum: "2"
```
## Templating
You can also utilize go templating in your JSON schema in order to match one field against another.
E.g. here is the built-in check to ensure that the `name` annotation matches the object's name:
```yaml
successMessage: Label app.kubernetes.io/name matches metadata.name
failureMessage: Label app.kubernetes.io/name must match metadata.name
target: Controller
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
required: ["labels"]
properties:
labels:
type: object
required: ["app.kubernetes.io/name"]
properties:
app.kubernetes.io/name:
const: "{{ .metadata.name }}"
```
You can also use the full [Go template syntax](https://golang.org/pkg/text/template/), though
you may need to specify your schema as a string in order to use concepts like `range`. E.g.
this check ensures that at least one of the object's labels is present in `matchLabels`:
```yaml
schemaString: |
type: object
properties:
spec:
type: object
required: ["selector"]
properties:
selector:
type: object
required: ["matchLabels"]
properties:
matchLabels:
type: object
anyOf:
{{ range $key, $value := .metadata.labels }}
- properties:
"{{ $key }}":
type: string
const: {{ $value }}
required: ["{{ $key }}"]
{{ end }}
```
## Multi-Resource Checks
You can write checks that span multiple resources. This is helpful for ensuring e.g.
that every Deployment has a PDB or an HPA associated with it.
Here's the check to ensure that every Deployment has a PDB:
```yaml
successMessage: A PodDisruptionBudget is attached
failureMessage: Should have a PodDisruptionBudget
category: Reliability
target: Controller
controllers:
include:
- Deployment
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
metadata:
type: object
properties:
labels:
type: object
minProperties: 1
additionalSchemaStrings:
policy/PodDisruptionBudget: |
type: object
properties:
spec:
type: object
required: ["selector"]
properties:
selector:
type: object
required: ["matchLabels"]
properties:
matchLabels:
type: object
anyOf:
{{ range $key, $value := .metadata.labels }}
- properties:
"{{ $key }}":
type: string
const: {{ $value }}
required: ["{{ $key }}"]
{{ end }}
```
## JSON vs YAML
Schemas can also be specified as JSON strings instead of YAML, for easier copy/pasting:
```yaml
customChecks:
@@ -202,3 +32,8 @@ customChecks:
}
```
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
strings like `1000m` and `1G`. You can see an example in [the extended config](https://github.com/FairwindsOps/polaris/tree/master/examples/config-full.yaml)
There are additional examples in the [checks folder](https://github.com/FairwindsOps/polaris/tree/master/checks).
+1 -1
View File
@@ -19,7 +19,7 @@ kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
### Helm
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris --create-namespace
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
-8
View File
@@ -55,14 +55,6 @@ polaris audit --audit-path ./deploy/ \
--only-show-failed-tests true
```
### Audit Helm Charts
You can audit helm charts using the `--helm-chart` and `--helm-values` flags:
```
polaris audit \
--helm-chart ./deploy/chart \
--helm-values ./deploy/chart/values.yml
```
### As Github Action
#### Setup polaris action
+108 -76
View File
@@ -2516,42 +2516,16 @@
}
},
"browserslist": {
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
"version": "4.14.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.7.tgz",
"integrity": "sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001219",
"colorette": "^1.2.2",
"electron-to-chromium": "^1.3.723",
"caniuse-lite": "^1.0.30001157",
"colorette": "^1.2.1",
"electron-to-chromium": "^1.3.591",
"escalade": "^3.1.1",
"node-releases": "^1.1.71"
},
"dependencies": {
"caniuse-lite": {
"version": "1.0.30001239",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz",
"integrity": "sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==",
"dev": true
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
},
"electron-to-chromium": {
"version": "1.3.752",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz",
"integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==",
"dev": true
},
"node-releases": {
"version": "1.1.73",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz",
"integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==",
"dev": true
}
"node-releases": "^1.1.66"
}
},
"buffer": {
@@ -2907,6 +2881,18 @@
"integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
"dev": true
},
"clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
"integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
"dev": true,
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -2988,9 +2974,9 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz",
"integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==",
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz",
"integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==",
"dev": true,
"requires": {
"color-name": "^1.0.0",
@@ -3806,6 +3792,13 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"dev": true,
"optional": true
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -3873,9 +3866,9 @@
"dev": true
},
"dns-packet": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
"integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
"integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
"dev": true,
"requires": {
"ip": "^1.1.0",
@@ -4019,25 +4012,31 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true
},
"electron-to-chromium": {
"version": "1.3.599",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.599.tgz",
"integrity": "sha512-u6VGpFsIzSCNrWJb1I72SUypz3EGoBaiEgygoMkd0IOcGR3WF3je5VTx9OIRI9Qd8UOMHinLImyJFkYHTq6nsg==",
"dev": true
},
"elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true,
"requires": {
"bn.js": "^4.11.9",
"brorand": "^1.1.0",
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.1",
"inherits": "^2.0.4",
"minimalistic-assert": "^1.0.1",
"minimalistic-crypto-utils": "^1.0.1"
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
},
"dependencies": {
"bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true
}
}
@@ -4839,6 +4838,16 @@
"slash": "^2.0.0"
}
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"dev": true,
"optional": true,
"requires": {
"delegate": "^3.1.2"
}
},
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
@@ -5338,9 +5347,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"internal-ip": {
@@ -5913,9 +5922,9 @@
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
},
"lodash._reinterpolate": {
@@ -6462,6 +6471,12 @@
}
}
},
"node-releases": {
"version": "1.1.67",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz",
"integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==",
"dev": true
},
"nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
@@ -7008,9 +7023,9 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
"postcss": {
"version": "7.0.36",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
@@ -7644,10 +7659,13 @@
"dev": true
},
"prismjs": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.0.tgz",
"integrity": "sha512-SqV5GRsNqnzCL8k5dfAjCNhUrF3pR0A9lTDSCUZeh/LIshheXJEaP0hwLz2t4XHivd2J/v2HR+gRnigzeKe3cQ==",
"dev": true
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.22.0.tgz",
"integrity": "sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w==",
"dev": true,
"requires": {
"clipboard": "^2.0.0"
}
},
"process": {
"version": "0.11.10",
@@ -8269,6 +8287,13 @@
}
}
},
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"dev": true,
"optional": true
},
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -8809,9 +8834,9 @@
}
},
"ssri": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
"dev": true,
"requires": {
"figgy-pudding": "^3.5.1"
@@ -9272,6 +9297,13 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"dev": true,
"optional": true
},
"to-arraybuffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
@@ -9723,9 +9755,9 @@
}
},
"url-parse": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"dev": true,
"requires": {
"querystringify": "^2.1.1",
@@ -10566,9 +10598,9 @@
}
},
"ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"dev": true,
"requires": {
"async-limiter": "~1.0.0"
@@ -10587,9 +10619,9 @@
"dev": true
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yallist": {
-4
View File
@@ -6,10 +6,6 @@ checks:
pullPolicyNotAlways: warning
readinessProbeMissing: warning
livenessProbeMissing: warning
metadataAndNameMismatched: ignore
pdbDisruptionsIsZero: warning
missingPodDisruptionBudget: ignore
# efficiency
cpuRequestsMissing: warning
cpuLimitsMissing: warning
+21 -9
View File
@@ -1,27 +1,39 @@
module github.com/fairwindsops/polaris
go 1.15
go 1.13
require (
cloud.google.com/go v0.74.0 // indirect
github.com/Azure/go-autorest/autorest v0.11.15 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.10 // indirect
github.com/fatih/color v1.12.0
github.com/fatih/color v1.10.0
github.com/gobuffalo/packr/v2 v2.8.1
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.1.3 // indirect
github.com/googleapis/gnostic v0.5.3 // indirect
github.com/gorilla/mux v1.8.0
github.com/imdario/mergo v0.3.11 // indirect
github.com/karrick/godirwalk v1.16.1 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/prometheus/client_golang v1.9.0 // indirect
github.com/qri-io/jsonschema v0.1.1
github.com/rogpeppe/go-internal v1.6.2 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/thoas/go-funk v0.9.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/api v0.21.3
k8s.io/apimachinery v0.21.3
k8s.io/client-go v0.21.2
sigs.k8s.io/controller-runtime v0.9.2
github.com/thoas/go-funk v0.7.0
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
k8s.io/api v0.20.4
k8s.io/apimachinery v0.20.4
k8s.io/client-go v0.20.4
k8s.io/component-base v0.20.1 // indirect
sigs.k8s.io/controller-runtime v0.7.0
sigs.k8s.io/yaml v1.2.0
)
+348 -209
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -20,7 +20,7 @@ import (
const (
// Version represents the current release version of Polaris
Version = "4.0.7"
Version = "3.2.0"
)
func main() {
-60
View File
@@ -1,60 +0,0 @@
package config
import (
"github.com/gobuffalo/packr/v2"
"github.com/sirupsen/logrus"
)
var (
// BuiltInChecks contains the checks that come pre-installed w/ Polaris
BuiltInChecks = map[string]SchemaCheck{}
schemaBox = (*packr.Box)(nil)
// We explicitly set the order to avoid thrash in the
// tests as we migrate toward JSON schema
checkOrder = []string{
// Controller Checks
"multipleReplicasForDeployment",
// Pod checks
"hostIPCSet",
"hostPIDSet",
"hostNetworkSet",
// Container checks
"memoryLimitsMissing",
"memoryRequestsMissing",
"cpuLimitsMissing",
"cpuRequestsMissing",
"readinessProbeMissing",
"livenessProbeMissing",
"pullPolicyNotAlways",
"tagNotSpecified",
"hostPortSet",
"runAsRootAllowed",
"runAsPrivileged",
"notReadOnlyRootFilesystem",
"privilegeEscalationAllowed",
"dangerousCapabilities",
"insecureCapabilities",
"priorityClassNotSet",
// Other checks
"tlsSettingsMissing",
"pdbDisruptionsIsZero",
"metadataAndNameMismatched",
"missingPodDisruptionBudget",
}
)
func init() {
schemaBox = packr.New("Schemas", "../../checks")
for _, checkID := range checkOrder {
contents, err := schemaBox.Find(checkID + ".yaml")
if err != nil {
panic(err)
}
check, err := ParseCheck(checkID, contents)
if err != nil {
logrus.Errorf("Error while parsing check %s", checkID)
panic(err)
}
BuiltInChecks[checkID] = check
}
}
+6 -12
View File
@@ -16,7 +16,6 @@ package config
import (
"context"
"fmt"
"io"
"log"
"net/http"
@@ -143,9 +142,7 @@ func TestConfigNoServerError(t *testing.T) {
func TestConfigWithCustomChecks(t *testing.T) {
valid := map[string]interface{}{
"securityContext": map[string]interface{}{
"foo": "bar",
},
"securityContext": map[string]interface{}{},
}
invalid := map[string]interface{}{
"notSecurityContext": map[string]interface{}{},
@@ -154,23 +151,20 @@ func TestConfigWithCustomChecks(t *testing.T) {
parsedConf, err := Parse([]byte(confCustomChecks))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]interface{}{})
isValid, _, err := check.CheckObject(valid)
isValid, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
assert.NoError(t, err)
assert.Equal(t, true, isValid)
isValid, _, err = check.CheckObject(invalid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(valid)
assert.NoError(t, err)
if !assert.Equal(t, true, isValid) {
fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
}
isValid, _, err = check.CheckObject(invalid)
assert.Equal(t, true, isValid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
}
+21 -136
View File
@@ -1,19 +1,14 @@
package config
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"text/template"
"github.com/qri-io/jsonschema"
"github.com/thoas/go-funk"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
)
// TargetKind represents the part of the config to be validated
@@ -37,50 +32,21 @@ var HandledTargets = []TargetKind{
// SchemaCheck is a Polaris check that runs using JSON Schema
type SchemaCheck struct {
ID string `yaml:"id" json:"id"`
Category string `yaml:"category" json:"category"`
SuccessMessage string `yaml:"successMessage" json:"successMessage"`
FailureMessage string `yaml:"failureMessage" json:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers" json:"controllers"`
Containers includeExcludeList `yaml:"containers" json:"containers"`
Target TargetKind `yaml:"target" json:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget" json:"schemaTarget"`
Schema map[string]interface{} `yaml:"schema" json:"schema"`
SchemaString string `yaml:"schemaString" json:"schemaString"`
Validator jsonschema.RootSchema `yaml:"-" json:"-"`
AdditionalSchemas map[string]map[string]interface{} `yaml:"additionalSchemas" json:"additionalSchemas"`
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
AdditionalValidators map[string]jsonschema.RootSchema `yaml:"-" json:"-"`
ID string `yaml:"id"`
Category string `yaml:"category"`
SuccessMessage string `yaml:"successMessage"`
FailureMessage string `yaml:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers"`
Containers includeExcludeList `yaml:"containers"`
Target TargetKind `yaml:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget"`
Schema jsonschema.RootSchema `yaml:"schema"`
JSONSchema string `yaml:"jsonSchema"`
}
type resourceMinimum string
type resourceMaximum string
func unmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
reader := bytes.NewReader(raw)
d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
if err := d.Decode(dest); err != nil {
if err == io.EOF {
break
}
return fmt.Errorf("Decoding schema check failed: %v", err)
}
}
return nil
}
// ParseCheck parses a check from a byte array
func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
check := SchemaCheck{}
err := unmarshalYAMLOrJSON(rawBytes, &check)
if err != nil {
return check, err
}
check.Initialize(id)
return check, nil
}
func init() {
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
@@ -160,119 +126,38 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
// Initialize sets up the schema
func (check *SchemaCheck) Initialize(id string) error {
check.ID = id
if check.SchemaString == "" {
jsonBytes, err := json.Marshal(check.Schema)
if err != nil {
if check.JSONSchema != "" {
if err := json.Unmarshal([]byte(check.JSONSchema), &check.Schema); err != nil {
return err
}
check.SchemaString = string(jsonBytes)
}
for kind, schema := range check.AdditionalSchemas {
jsonBytes, err := json.Marshal(schema)
if err != nil {
return err
}
check.AdditionalSchemaStrings[kind] = string(jsonBytes)
}
check.Schema = map[string]interface{}{}
check.AdditionalSchemas = map[string]map[string]interface{}{}
return nil
}
// TemplateForResource fills out a check's templated fields given a particular resource
func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, error) {
newCheck := check // Make a copy of the check, since we're going to modify the schema
templateStrings := map[string]string{
"": newCheck.SchemaString,
}
for kind, schema := range newCheck.AdditionalSchemaStrings {
templateStrings[kind] = schema
}
newCheck.SchemaString = ""
newCheck.AdditionalSchemaStrings = map[string]string{}
for kind, tmplString := range templateStrings {
tmpl := template.New(newCheck.ID)
tmpl, err := tmpl.Parse(tmplString)
if err != nil {
return nil, err
}
w := bytes.Buffer{}
err = tmpl.Execute(&w, res)
if err != nil {
return nil, err
}
if kind == "" {
newCheck.SchemaString = w.String()
} else {
newCheck.AdditionalSchemaStrings[kind] = w.String()
}
}
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
val := jsonschema.RootSchema{}
err := unmarshalYAMLOrJSON([]byte(schemaStr), &val)
if err != nil {
return nil, err
}
newCheck.AdditionalValidators[kind] = val
}
err := unmarshalYAMLOrJSON([]byte(newCheck.SchemaString), &newCheck.Validator)
if err != nil {
return nil, err
}
return &newCheck, err
}
// CheckPod checks a pod spec against the schema
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
return check.CheckObject(pod)
}
// CheckController checks a controler's spec against the schema
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
errs, err := check.Validator.ValidateBytes(bytes)
return len(errs) == 0, errs, err
func (check SchemaCheck) CheckController(bytes []byte) (bool, error) {
errs, err := check.Schema.ValidateBytes(bytes)
return len(errs) == 0, err
}
// CheckContainer checks a container spec against the schema
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
return check.CheckObject(container)
}
// CheckObject checks arbitrary data against the schema
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
bytes, err := json.Marshal(obj)
if err != nil {
return false, nil, err
return false, err
}
errs, err := check.Validator.ValidateBytes(bytes)
return len(errs) == 0, errs, err
}
// CheckAdditionalObjects looks for an object that passes the specified additional schema
func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []interface{}) (bool, error) {
val, ok := check.AdditionalValidators[groupkind]
if !ok {
return false, errors.New("No validator found for " + groupkind)
}
for _, obj := range objects {
bytes, err := json.Marshal(obj)
if err != nil {
return false, err
}
errs, err := val.ValidateBytes(bytes)
if err != nil {
return false, err
}
if len(errs) == 0 {
return true, nil
}
}
return false, nil
errs, err := check.Schema.ValidateBytes(bytes)
return len(errs) == 0, err
}
// IsActionable decides if this check applies to a particular target
+2 -4
View File
@@ -13,10 +13,8 @@
padding-right: 40px;
box-shadow: none;
}
.card.transparent {
background-color: transparent;
border: none;
box-shadow: none;
.card.insights img {
max-width: 400px;
}
.card.insights a {
background-color: #20162D;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 34 KiB

+2 -2
View File
@@ -181,7 +181,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
}
var auditDataObj validator.AuditData
auditDataObj, err = validator.RunAudit(adjustedConf, k)
auditDataObj, err = validator.RunAudit(adjustedConf, k, false)
if err != nil {
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
return
@@ -214,7 +214,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
}
var auditData validator.AuditData
auditData, err = validator.RunAudit(adjustedConf, k)
auditData, err = validator.RunAudit(adjustedConf, k, false)
if err != nil {
logrus.Errorf("Error getting audit data: %v", err)
http.Error(w, "Error running audit", 500)
-241
View File
@@ -1,241 +0,0 @@
package dashboard
import (
"testing"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/stretchr/testify/assert"
)
func TestWarningWidth(t *testing.T) {
input1 := validator.CountSummary{
Successes: 0,
Warnings: 0,
Dangers: 0,
}
input2 := 6
expectedOutput := uint(0x6)
actual := getWarningWidth(input1, input2)
assert.Equal(t, expectedOutput, actual)
input1 = validator.CountSummary{
Successes: 10,
Warnings: 3,
Dangers: 1,
}
input2 = 3
expectedOutput = uint(0x2)
actual = getWarningWidth(input1, input2)
assert.Equal(t, expectedOutput, actual)
}
func TestSuccessWidth(t *testing.T) {
input1 := validator.CountSummary{
Successes: 0,
Warnings: 0,
Dangers: 0,
}
input2 := 6
expectedOutput := uint(0x6)
actual := getSuccessWidth(input1, input2)
assert.Equal(t, expectedOutput, actual)
input1 = validator.CountSummary{
Successes: 8,
Warnings: 6,
Dangers: 4,
}
input2 = 7
expectedOutput = uint(0x3)
actual = getSuccessWidth(input1, input2)
assert.Equal(t, expectedOutput, actual)
}
func TestGetGrade(t *testing.T) {
input := validator.CountSummary{
Successes: 10,
Warnings: 3,
Dangers: 1,
}
expectedOutput := "B-"
actual := getGrade(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "A+", actual)
}
func TestGetWeatherIcon(t *testing.T) {
input := validator.CountSummary{
Successes: 10,
Warnings: 3,
Dangers: 1,
}
expectedOutput := "fa-cloud-sun"
actual := getWeatherIcon(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "fa-cloud-showers-heavy", actual)
}
func TestGetResultClass(t *testing.T) {
input := validator.ResultMessage{
ID: "",
Message: "",
Details: []string(nil),
Success: false,
Severity: "",
Category: "",
}
expectedOutput := " failure"
actual := getResultClass(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, " success", actual)
input = validator.ResultMessage{
ID: "",
Message: "",
Details: []string(nil),
Success: true,
Severity: "",
Category: "",
}
expectedOutput = " success"
actual = getResultClass(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, " failure", actual)
}
func TestGetWeatherText(t *testing.T) {
input := validator.CountSummary{
Successes: 10,
Warnings: 3,
Dangers: 1,
}
expectedOutput := "Mostly smooth sailing"
actual := getWeatherText(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "Storms ahead, be careful", actual)
}
func TestGetIcon(t *testing.T) {
input := validator.ResultMessage{
ID: "",
Message: "",
Details: []string(nil),
Success: false,
Severity: "",
Category: "",
}
expectedOutput := "fas fa-times"
actual := getIcon(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "fas fa-check", actual)
input = validator.ResultMessage{
ID: "",
Message: "",
Details: []string(nil),
Success: true,
Severity: "",
Category: "",
}
expectedOutput = "fas fa-check"
actual = getIcon(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "fas fa-times", actual)
input = validator.ResultMessage{
ID: "",
Message: "",
Details: []string(nil),
Success: false,
Severity: config.SeverityWarning,
Category: "",
}
expectedOutput = "fas fa-exclamation"
actual = getIcon(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "fas fa-times", actual)
}
func TestGetCategoryLink(t *testing.T) {
input := "Efficiency"
expectedOutput := "https://polaris.docs.fairwinds.com/checks/efficiency"
actual := getCategoryLink(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "ttps://polaris.docs.fairwinds.com/checks/reliability", actual)
}
func TestGetCategoryInfo(t *testing.T) {
input := "Security"
expectedOutput :=
`
Kubernetes provides a great deal of configurability when it comes to the
security of your workloads. A key principle here involves limiting the level
of access any individual workload has. Polaris has validations for a number of
best practices, mostly focused on ensuring that unnecessary access has not
been granted to an application workload.
`
actual := getCategoryInfo(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "fas fa-times", actual)
input = "Reliability"
expectedOutput =
`
Kubernetes is built to reliabily run highly available applications.
Polaris includes a number of checks to ensure that you are maximizing
the reliability potential of Kubernetes.
`
actual = getCategoryInfo(input)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, "fas fa-times", actual)
}
func TestStringInSlice(t *testing.T) {
input1 := "a"
input2 := []string{"a", "b", "cde"}
expectedOutput := true
actual := stringInSlice(input1, input2)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, false, actual)
input1 = "f"
input2 = []string{"a", "b", "cde"}
expectedOutput = false
actual = stringInSlice(input1, input2)
assert.Equal(t, expectedOutput, actual)
assert.NotEqual(t, true, actual)
}
+4 -6
View File
@@ -83,12 +83,10 @@
</div>
</div>
<div class="card transparent">
<a class="align-self-center"
target="_blank"
href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
<img src="static/images/FW_Insights_Polaris.svg" />
</a>
<div id="insights" class="card insights py-2">
<img class="align-self-center" src="static/images/FW_Insights_Polaris.svg" />
<span>Fairwinds, the company behind Polaris, now offers Fairwinds Insights, a multi-cluster, multi-user Kubernetes configuration validation and policy enforcement platform. Fairwinds Insights can help you avoid errors that lead to wasted time, compute costs, and increased risk.</span>
<a class="col-auto py-1 px-3 mt-2 align-self-center" href="https://fairwinds.com/insights?source=polaris" target="_blank">Try Insights</a>
</div>
<div id="categories" class="card category">
+1 -1
View File
@@ -23,7 +23,7 @@
<hr class="col-11">
<div class="align-self-center d-flex flex-column justify-content-center">
<img class="row mb-4 fw-logo" src="static/images/purple_logo_fairwinds.svg" alt="Fairwinds" />
<a class="row justify-content-center" href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">&copy; 2020 Fairwinds Ops Inc.</a>
<a class="row justify-content-center" href="https://fairwinds.com?source=polaris" target="_blank">&copy; 2020 Fairwinds Ops Inc.</a>
</div>
</div>
{{end}}
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="navbar">
<div class="fw-nav">
<div class="container p-2">
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">
<a href="https://fairwinds.com?source=polaris" target="_blank">
<img class="fw-logo" src="static/images/white_logo_fairwinds.svg" alt="Fairwinds" />
</a>
<div class="right-section p-0 d-flex justify-content-between">
+8 -6
View File
@@ -25,23 +25,24 @@ type GenericResource struct {
}
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured) (GenericResource, error) {
func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (GenericResource, error) {
workload := GenericResource{
Kind: unst.GetKind(),
Resource: unst,
Resource: *unst,
}
objMeta, err := meta.Accessor(&unst)
objMeta, err := meta.Accessor(unst)
if err != nil {
return workload, err
}
workload.ObjectMeta = objMeta
b, err := json.Marshal(&unst)
b, err := json.Marshal(unst)
if err != nil {
return workload, err
}
workload.OriginalObjectJSON = b
m := make(map[string]interface{})
err = json.Unmarshal(b, &m)
if err != nil {
@@ -60,6 +61,7 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured) (Generic
}
workload.PodSpec = &podSpec
}
return workload, nil
}
@@ -99,7 +101,7 @@ func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
if err != nil {
return GenericResource{}, err
}
return NewGenericResourceFromUnstructured(unst)
return NewGenericResourceFromUnstructured(&unst)
}
// ResolveControllerFromPod builds a new workload for a given Pod
@@ -159,7 +161,7 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
if lastKey != "" {
unst := objectCache[lastKey]
return NewGenericResourceFromUnstructured(unst)
return NewGenericResourceFromUnstructured(&unst)
}
workload, err := NewGenericResourceFromPod(podResource, podResource)
if err != nil {
+9 -37
View File
@@ -43,9 +43,7 @@ type ResourceProvider struct {
type resourceKindMap map[string][]GenericResource
func (rkm resourceKindMap) addResource(r GenericResource) {
gvk := r.Resource.GroupVersionKind()
key := gvk.Group + "/" + gvk.Kind
rkm[key] = append(rkm[key], r)
rkm[r.Kind] = append(rkm[r.Kind], r)
}
func (rkm resourceKindMap) addResources(rs []GenericResource) {
@@ -75,24 +73,11 @@ func (rkm resourceKindMap) GetNumberOfControllers() int {
}
// This is here for backward compatibility reasons
func maybeTransformKindIntoGroupKind(k string) string {
func maybeTransformKindIntoGroupKind(k conf.TargetKind) string {
if k == "Ingress" {
return "networking.k8s.io/Ingress"
} else if k == "PodDisruptionBudget" {
return "policy/PodDisruptionBudget"
}
return k
}
func parseGroupKind(gk string) schema.GroupKind {
i := strings.Index(gk, "/")
if i == -1 {
return schema.GroupKind{Kind: gk}
}
group := gk[:i]
kind := gk[i+1:]
return schema.GroupKind{Group: group, Kind: kind}
return ""
}
func newResourceProvider(version, sourceType, sourceName string) ResourceProvider {
@@ -168,7 +153,7 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
logrus.Errorf("Could not find workload %s: %v", workload, err)
return nil, err
}
workloadObj, err := NewGenericResourceFromUnstructured(*obj)
workloadObj, err := NewGenericResourceFromUnstructured(obj)
if err != nil {
logrus.Errorf("Could not parse workload %s: %v", workload, err)
return nil, err
@@ -263,29 +248,16 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
return nil, err
}
restMapper := restmapper.NewDiscoveryRESTMapper(resources)
allChecks := []conf.SchemaCheck{}
for _, check := range c.CustomChecks {
allChecks = append(allChecks, check)
}
for _, check := range conf.BuiltInChecks {
allChecks = append(allChecks, check)
}
var additionalKinds []conf.TargetKind
for _, check := range allChecks {
neededKinds := []conf.TargetKind{check.Target}
for key := range check.AdditionalSchemaStrings {
neededKinds = append(neededKinds, conf.TargetKind(key))
}
for _, kind := range neededKinds {
if !funk.Contains(conf.HandledTargets, kind) {
additionalKinds = append(additionalKinds, kind)
}
for _, check := range c.CustomChecks {
if !funk.Contains(conf.HandledTargets, check.Target) {
additionalKinds = append(additionalKinds, check.Target)
}
}
for _, kind := range additionalKinds {
groupKind := parseGroupKind(maybeTransformKindIntoGroupKind(string(kind)))
groupKind := schema.ParseGroupKind(maybeTransformKindIntoGroupKind(kind))
mapping, err := (restMapper).RESTMapping(groupKind)
if err != nil {
logrus.Warnf("Error retrieving mapping of Kind %s because of error: %v", kind, err)
@@ -298,7 +270,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
return nil, err
}
for _, obj := range objects.Items {
res, err := NewGenericResourceFromUnstructured(obj)
res, err := NewGenericResourceFromUnstructured(&obj)
if err != nil {
return nil, err
}
+7 -5
View File
@@ -3,6 +3,7 @@ package kube
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"testing"
"time"
@@ -28,7 +29,8 @@ func TestGetResourcesFromPath(t *testing.T) {
assert.Equal(t, "two", provider.Namespaces[0].ObjectMeta.Name)
namespaceCount := map[string]int{}
for _, resources := range provider.Resources {
for kind, resources := range provider.Resources {
fmt.Println("found", kind, len(resources))
for _, controller := range resources {
namespaceCount[controller.ObjectMeta.GetNamespace()]++
}
@@ -50,8 +52,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["Deployment"]), "Should have one controller")
assert.Equal(t, "dashboard", resources.Resources["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)
@@ -73,8 +75,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["Deployment"]), "Should have one controller")
assert.Equal(t, "dashboard", resources.Resources["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)
+6 -6
View File
@@ -29,18 +29,18 @@ import (
func TestValidatePDB(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"pdbDisruptionsIsZero": conf.SeverityWarning,
"pdbDisruptionsAllowedGreaterThanZero": conf.SeverityWarning,
},
}
pdb := unstructured.Unstructured{}
res, err := kube.NewGenericResourceFromUnstructured(pdb)
res, err := kube.NewGenericResourceFromUnstructured(&pdb)
res.Kind = "PodDisruptionBudget"
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
actualResult, err := applyNonControllerSchemaChecks(&c, res)
if err != nil {
panic(err)
}
results := actualResult.Results["pdbDisruptionsIsZero"]
results := actualResult.Results["pdbDisruptionsAllowedGreaterThanZero"]
assert.False(t, results.Success)
assert.Equal(t, conf.SeverityWarning, results.Severity)
@@ -70,13 +70,13 @@ func TestValidateIngress(t *testing.T) {
if err != nil {
panic(err)
}
res, err := kube.NewGenericResourceFromUnstructured(unst)
res, err := kube.NewGenericResourceFromUnstructured(&unst)
if err != nil {
panic(err)
}
res.Kind = "Ingress"
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
actualResult, err := applyNonControllerSchemaChecks(&c, res)
if err != nil {
panic(err)
}
+13 -16
View File
@@ -69,23 +69,20 @@ 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(&parsedConf, workload, container, false)
if err != nil {
panic(err)
}
summary := results.GetSummary()
if assert.Equal(t, uint(len(expectedWarnings)), summary.Warnings) {
assert.ElementsMatch(t, expectedWarnings, results.GetWarnings())
}
assert.Equal(t, uint(len(expectedWarnings)), summary.Warnings)
assert.ElementsMatch(t, expectedWarnings, results.GetWarnings())
if assert.Equal(t, uint(len(expectedDangers)), summary.Dangers) {
assert.ElementsMatch(t, expectedDangers, results.GetDangers())
}
assert.Equal(t, uint(len(expectedDangers)), summary.Dangers)
assert.ElementsMatch(t, expectedDangers, results.GetDangers())
if assert.Equal(t, uint(len(expectedSuccesses)), summary.Successes) {
assert.ElementsMatch(t, expectedSuccesses, results.GetSuccesses())
}
assert.Equal(t, uint(len(expectedSuccesses)), summary.Successes)
assert.ElementsMatch(t, expectedSuccesses, results.GetSuccesses())
}
func TestValidateResourcesEmptyConfig(t *testing.T) {
@@ -93,7 +90,7 @@ func TestValidateResourcesEmptyConfig(t *testing.T) {
Name: "Empty",
}
results, err := applyContainerSchemaChecks(&conf.Configuration{}, nil, getEmptyWorkload(t, ""), container, false)
results, err := applyContainerSchemaChecks(&conf.Configuration{}, getEmptyWorkload(t, ""), container, false)
if err != nil {
panic(err)
}
@@ -190,7 +187,7 @@ func TestValidateHealthChecks(t *testing.T) {
for idx, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
controller := getEmptyWorkload(t, "")
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.probes}, nil, controller, tt.container, tt.isInit)
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.probes}, controller, tt.container, tt.isInit)
if err != nil {
panic(err)
}
@@ -304,7 +301,7 @@ func TestValidateImage(t *testing.T) {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
controller := getEmptyWorkload(t, "")
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.image}, nil, controller, tt.container, false)
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.image}, controller, tt.container, false)
if err != nil {
panic(err)
}
@@ -421,7 +418,7 @@ func TestValidateNetworking(t *testing.T) {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
controller := getEmptyWorkload(t, "")
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.networkConf}, nil, controller, tt.container, false)
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.networkConf}, controller, tt.container, false)
if err != nil {
panic(err)
}
@@ -926,7 +923,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(&conf.Configuration{Checks: tt.securityConf}, workload, tt.container, false)
if err != nil {
panic(err)
}
@@ -1071,7 +1068,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(&config, workload, tt.container, false)
if err != nil {
panic(err)
}
+9 -9
View File
@@ -49,7 +49,7 @@ func TestValidateController(t *testing.T) {
}
var actualResult Result
actualResult, err = applyControllerSchemaChecks(&c, nil, deployment)
actualResult, err = applyControllerSchemaChecks(&c, 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(&c, controller)
if err != nil {
panic(err)
}
@@ -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(&c, 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(&c, 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(&c, 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(&c, 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(&c, 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(&c, 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(&c, resources)
if err != nil {
panic(err)
}
+11 -4
View File
@@ -16,15 +16,19 @@ import (
)
// RunAudit runs a full Polaris audit and returns an AuditData object
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, onlyShowFailedTests bool) (AuditData, error) {
displayName := config.DisplayName
if displayName == "" {
displayName = kubeResources.SourceName
}
results, err := ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
if err != nil {
return AuditData{}, err
results := []Result{}
for _, resources := range kubeResources.Resources {
kindResults, err := ApplyAllSchemaChecksToAllResources(&config, resources)
if err != nil {
return AuditData{}, err
}
results = append(results, kindResults...)
}
auditData := AuditData{
@@ -42,6 +46,9 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
Results: results,
}
auditData.Score = auditData.GetSummary().GetScore()
if onlyShowFailedTests {
auditData.RemoveSuccessfulResults()
}
return auditData, nil
}
+1 -1
View File
@@ -31,7 +31,7 @@ func TestGetTemplateData(t *testing.T) {
score := uint(0)
var actualAudit AuditData
actualAudit, err = RunAudit(c, resources)
actualAudit, err = RunAudit(c, resources, false)
assert.Equal(t, err, nil, "error should be nil")
assert.Equal(t, score, actualAudit.Score, "")
assert.EqualValues(t, sum, actualAudit.GetSummary())
+20 -32
View File
@@ -19,7 +19,6 @@ import (
"time"
"github.com/fatih/color"
"github.com/thoas/go-funk"
"github.com/fairwindsops/polaris/pkg/config"
)
@@ -53,12 +52,10 @@ type AuditData struct {
}
// RemoveSuccessfulResults remove all test that have passed.
func (res AuditData) RemoveSuccessfulResults() AuditData {
resCopy := res
resCopy.Results = funk.Map(res.Results, func(auditDataResult Result) Result {
return auditDataResult.removeSuccessfulResults()
}).([]Result)
return resCopy
func (res *AuditData) RemoveSuccessfulResults() {
for _, auditDataResult := range res.Results {
auditDataResult.removeSuccessfulResults()
}
}
// ClusterInfo contains Polaris results as well as some high-level stats
@@ -74,7 +71,6 @@ type ClusterInfo struct {
type ResultMessage struct {
ID string
Message string
Details []string
Success bool
Severity config.Severity
Category string
@@ -83,14 +79,12 @@ type ResultMessage struct {
// ResultSet contiains the results for a set of checks
type ResultSet map[string]ResultMessage
func (res ResultSet) removeSuccessfulResults() ResultSet {
newResults := ResultSet{}
func (res ResultSet) removeSuccessfulResults() {
for k, resultMessage := range res {
if !resultMessage.Success {
newResults[k] = resultMessage
if resultMessage.Success {
delete(res, k)
}
}
return newResults
}
// Result provides results for a Kubernetes object
@@ -103,14 +97,9 @@ type Result struct {
CreatedTime time.Time
}
func (res Result) removeSuccessfulResults() Result {
resCopy := res
resCopy.Results = res.Results.removeSuccessfulResults()
if res.PodResult != nil {
podCopy := res.PodResult.removeSuccessfulResults()
resCopy.PodResult = &podCopy
}
return resCopy
func (res *Result) removeSuccessfulResults() {
res.Results.removeSuccessfulResults()
res.PodResult.removeSuccessfulResults()
}
// PodResult provides a list of validation messages for each pod.
@@ -120,13 +109,14 @@ type PodResult struct {
ContainerResults []ContainerResult
}
func (res PodResult) removeSuccessfulResults() PodResult {
resCopy := PodResult{}
resCopy.Results = res.Results.removeSuccessfulResults()
resCopy.ContainerResults = funk.Map(res.ContainerResults, func(containerResult ContainerResult) ContainerResult {
return containerResult.removeSuccessfulResults()
}).([]ContainerResult)
return resCopy
func (res *PodResult) removeSuccessfulResults() {
if res == nil {
return
}
res.Results.removeSuccessfulResults()
for _, containerResult := range res.ContainerResults {
containerResult.removeSuccessfulResults()
}
}
// ContainerResult provides a list of validation messages for each container.
@@ -135,10 +125,8 @@ type ContainerResult struct {
Results ResultSet
}
func (res ContainerResult) removeSuccessfulResults() ContainerResult {
resCopy := res
resCopy.Results = res.Results.removeSuccessfulResults()
return resCopy
func (res *ContainerResult) removeSuccessfulResults() {
res.Results.removeSuccessfulResults()
}
func fillString(id string, l int) string {
+5 -5
View File
@@ -50,7 +50,7 @@ func TestValidatePod(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, nil, deployment)
actualPodResult, err := applyControllerSchemaChecks(&c, deployment)
if err != nil {
panic(err)
}
@@ -85,7 +85,7 @@ func TestInvalidIPCPod(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
if err != nil {
panic(err)
}
@@ -121,7 +121,7 @@ func TestInvalidNetworkPod(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
if err != nil {
panic(err)
}
@@ -157,7 +157,7 @@ func TestInvalidPIDPod(t *testing.T) {
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
if err != nil {
panic(err)
}
@@ -200,7 +200,7 @@ func TestExemption(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
if err != nil {
panic(err)
}
+103 -77
View File
@@ -1,25 +1,91 @@
package validator
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"github.com/qri-io/jsonschema"
"github.com/thoas/go-funk"
"github.com/gobuffalo/packr/v2"
corev1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
)
var (
schemaBox = (*packr.Box)(nil)
builtInChecks = map[string]config.SchemaCheck{}
// We explicitly set the order to avoid thrash in the
// tests as we migrate toward JSON schema
checkOrder = []string{
// Controller Checks
"multipleReplicasForDeployment",
// Pod checks
"hostIPCSet",
"hostPIDSet",
"hostNetworkSet",
// Container checks
"memoryLimitsMissing",
"memoryRequestsMissing",
"cpuLimitsMissing",
"cpuRequestsMissing",
"readinessProbeMissing",
"livenessProbeMissing",
"pullPolicyNotAlways",
"tagNotSpecified",
"hostPortSet",
"runAsRootAllowed",
"runAsPrivileged",
"notReadOnlyRootFilesystem",
"privilegeEscalationAllowed",
"dangerousCapabilities",
"insecureCapabilities",
"priorityClassNotSet",
// Other checks
"tlsSettingsMissing",
"pdbDisruptionsAllowedGreaterThanZero",
}
)
type schemaTestCase struct {
Target config.TargetKind
Resource kube.GenericResource
IsInitContianer bool
Container *corev1.Container
ResourceProvider *kube.ResourceProvider
Target config.TargetKind
Resource kube.GenericResource
IsInitContianer bool
Container *corev1.Container
}
func init() {
schemaBox = packr.New("Schemas", "../../checks")
for _, checkID := range checkOrder {
contents, err := schemaBox.Find(checkID + ".yaml")
if err != nil {
panic(err)
}
check, err := parseCheck(contents)
if err != nil {
panic(err)
}
check.ID = checkID
builtInChecks[checkID] = check
}
}
func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
reader := bytes.NewReader(rawBytes)
check := config.SchemaCheck{}
d := yaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
if err := d.Decode(&check); err != nil {
if err == io.EOF {
return check, nil
}
return check, fmt.Errorf("Decoding schema check failed: %v", err)
}
}
}
func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) {
@@ -28,7 +94,7 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
}
check, ok := conf.CustomChecks[checkID]
if !ok {
check, ok = config.BuiltInChecks[checkID]
check, ok = builtInChecks[checkID]
}
if !ok {
return nil, fmt.Errorf("Check %s not found", checkID)
@@ -44,25 +110,15 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) {
return nil, nil
}
checkPtr, err := check.TemplateForResource(test.Resource.Resource.Object)
if err != nil {
return nil, err
}
return checkPtr, nil
return &check, nil
}
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.ValError) ResultMessage {
details := []string{}
for _, issue := range issues {
details = append(details, issue.Message)
}
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool) ResultMessage {
result := ResultMessage{
ID: check.ID,
Severity: conf.Checks[check.ID],
Category: check.Category,
Success: passes,
// FIXME: need to fix the tests before adding this back
//Details: details,
}
if passes {
result.Message = check.SuccessMessage
@@ -89,24 +145,11 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
return false
}
// ApplyAllSchemaChecksToResourceProvider applies all available checks to a ResourceProvider
func ApplyAllSchemaChecksToResourceProvider(conf *config.Configuration, resourceProvider *kube.ResourceProvider) ([]Result, error) {
results := []Result{}
for _, resources := range resourceProvider.Resources {
kindResults, err := ApplyAllSchemaChecksToAllResources(conf, resourceProvider, resources)
if err != nil {
return results, err
}
results = append(results, kindResults...)
}
return results, nil
}
// ApplyAllSchemaChecksToAllResources applies available checks to a list of resources
func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resources []kube.GenericResource) ([]Result, error) {
func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resources []kube.GenericResource) ([]Result, error) {
results := []Result{}
for _, resource := range resources {
result, err := ApplyAllSchemaChecks(conf, resourceProvider, resource)
result, err := ApplyAllSchemaChecks(conf, resource)
if err != nil {
return results, err
}
@@ -116,37 +159,37 @@ func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resourceProv
}
// ApplyAllSchemaChecks applies available checks to a single resource
func ApplyAllSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
func ApplyAllSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) {
if resource.PodSpec == nil {
return applyNonControllerSchemaChecks(conf, resourceProvider, resource)
return applyNonControllerSchemaChecks(conf, resource)
}
return applyControllerSchemaChecks(conf, resourceProvider, resource)
return applyControllerSchemaChecks(conf, resource)
}
func applyNonControllerSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
func applyNonControllerSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) {
finalResult := Result{
Kind: resource.Kind,
Name: resource.ObjectMeta.GetName(),
Namespace: resource.ObjectMeta.GetNamespace(),
}
resultSet, err := applyTopLevelSchemaChecks(conf, resourceProvider, resource, false)
resultSet, err := applyTopLevelSchemaChecks(conf, resource, false)
finalResult.Results = resultSet
return finalResult, err
}
func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
func applyControllerSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) {
finalResult := Result{
Kind: resource.Kind,
Name: resource.ObjectMeta.GetName(),
Namespace: resource.ObjectMeta.GetNamespace(),
}
resultSet, err := applyTopLevelSchemaChecks(conf, resourceProvider, resource, true)
resultSet, err := applyTopLevelSchemaChecks(conf, resource, true)
if err != nil {
return finalResult, err
}
finalResult.Results = resultSet
podRS, err := applyPodSchemaChecks(conf, resourceProvider, resource)
podRS, err := applyPodSchemaChecks(conf, resource)
if err != nil {
return finalResult, err
}
@@ -157,7 +200,7 @@ func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *k
finalResult.PodResult = &podRes
for _, container := range resource.PodSpec.InitContainers {
results, err := applyContainerSchemaChecks(conf, resourceProvider, resource, &container, true)
results, err := applyContainerSchemaChecks(conf, resource, &container, true)
if err != nil {
return finalResult, err
}
@@ -168,7 +211,7 @@ func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *k
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
}
for _, container := range resource.PodSpec.Containers {
results, err := applyContainerSchemaChecks(conf, resourceProvider, resource, &container, false)
results, err := applyContainerSchemaChecks(conf, resource, &container, false)
if err != nil {
return finalResult, err
}
@@ -182,10 +225,9 @@ func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *k
return finalResult, nil
}
func applyTopLevelSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, res kube.GenericResource, isController bool) (ResultSet, error) {
func applyTopLevelSchemaChecks(conf *config.Configuration, res kube.GenericResource, isController bool) (ResultSet, error) {
test := schemaTestCase{
ResourceProvider: resources,
Resource: res,
Resource: res,
}
if isController {
test.Target = config.TargetController
@@ -193,22 +235,20 @@ func applyTopLevelSchemaChecks(conf *config.Configuration, resources *kube.Resou
return applySchemaChecks(conf, test)
}
func applyPodSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource) (ResultSet, error) {
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericResource) (ResultSet, error) {
test := schemaTestCase{
Target: config.TargetPod,
ResourceProvider: resources,
Resource: controller,
Target: config.TargetPod,
Resource: controller,
}
return applySchemaChecks(conf, test)
}
func applyContainerSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) {
func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) {
test := schemaTestCase{
Target: config.TargetContainer,
ResourceProvider: resources,
Resource: controller,
Container: container,
IsInitContianer: isInit,
Target: config.TargetContainer,
Resource: controller,
Container: container,
IsInitContianer: isInit,
}
return applySchemaChecks(conf, test)
}
@@ -236,40 +276,26 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
return nil, nil
}
var passes bool
var issues []jsonschema.ValError
if check.SchemaTarget != "" {
if check.SchemaTarget == config.TargetPod && check.Target == config.TargetContainer {
podCopy := *test.Resource.PodSpec
podCopy.InitContainers = []corev1.Container{}
podCopy.Containers = []corev1.Container{*test.Container}
passes, issues, err = check.CheckPod(&podCopy)
passes, err = check.CheckPod(&podCopy)
} else {
return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
}
} else if check.Target == config.TargetPod {
passes, issues, err = check.CheckPod(test.Resource.PodSpec)
passes, err = check.CheckPod(test.Resource.PodSpec)
} else if check.Target == config.TargetContainer {
passes, issues, err = check.CheckContainer(test.Container)
passes, err = check.CheckContainer(test.Container)
} else {
passes, issues, err = check.CheckObject(test.Resource.Resource.Object)
passes, err = check.CheckObject(test.Resource.Resource.Object)
}
if err != nil {
return nil, err
}
for groupkind := range check.AdditionalValidators {
if !passes {
break
}
resources := test.ResourceProvider.Resources[groupkind]
objects := funk.Map(resources, func(res kube.GenericResource) interface{} {
return res.Resource.Object
}).([]interface{})
passes, err = check.CheckAdditionalObjects(groupkind, objects)
if err != nil {
return nil, err
}
}
result := makeResult(conf, check, passes, issues)
result := makeResult(conf, check, passes)
return &result, nil
}
+2 -2
View File
@@ -144,14 +144,14 @@ func TestValidateResourcesInit(t *testing.T) {
assert.NoError(t, err, "Expected no error when parsing config")
var results ResultSet
results, err = applyContainerSchemaChecks(&parsedConf, nil, controller, emptyContainer, false)
results, err = applyContainerSchemaChecks(&parsedConf, controller, emptyContainer, false)
if err != nil {
panic(err)
}
assert.Equal(t, uint(1), results.GetSummary().Dangers)
assert.Equal(t, uint(1), results.GetSummary().Warnings)
results, err = applyContainerSchemaChecks(&parsedConf, nil, controller, emptyContainer, true)
results, err = applyContainerSchemaChecks(&parsedConf, controller, emptyContainer, true)
if err != nil {
panic(err)
}
+5 -4
View File
@@ -23,8 +23,7 @@ func (cs CountSummary) GetScore() uint {
if total == 0 {
return uint(100)
}
score := uint((float64(cs.Successes*2) / float64(total)) * 100)
return score
return uint((float64(cs.Successes*2) / float64(total)) * 100)
}
// AddSummary adds two CountSummaries together
@@ -125,8 +124,10 @@ func (c Result) GetSummaryByCategory() CountSummaryByCategory {
// GetSummary summarizes AuditData
func (a AuditData) GetSummary() CountSummary {
summary := CountSummary{}
for _, res := range a.Results {
summary.AddSummary(res.GetSummary())
for _, ctrlResult := range a.Results {
if ctrlResult.PodResult != nil {
summary.AddSummary(ctrlResult.GetSummary())
}
}
return summary
}
+49 -28
View File
@@ -16,6 +16,8 @@ package webhook
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -54,11 +56,37 @@ func NewWebhook(mgr manager.Manager, validator Validator) {
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
}
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, error) {
var controller kube.GenericResource
// GetObjectFromRawRequest returns the pod object and the controller's object from the raw json bytes.
func GetObjectFromRawRequest(raw []byte) (corev1.Pod, interface{}, error) {
pod := corev1.Pod{}
var originalObject interface{}
decoded := map[string]interface{}{}
err := json.Unmarshal(raw, &decoded)
if err != nil {
return pod, originalObject, err
}
podMap := kube.GetPodSpec(decoded)
if podMap == nil {
return pod, originalObject, errors.New("Object does not contain pods")
}
encoded, err := json.Marshal(podMap)
if err != nil {
return pod, originalObject, err
}
err = json.Unmarshal(encoded, &pod.Spec)
if err != nil {
return pod, originalObject, err
}
originalObject = decoded
return pod, originalObject, err
}
func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult, error) {
pod := corev1.Pod{}
var originalObject interface{}
var err error
if req.AdmissionRequest.Kind.Kind == "Pod" {
pod := corev1.Pod{}
err := v.decoder.Decode(req, &pod)
if err != nil {
return nil, err
@@ -67,64 +95,57 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.Result, er
logrus.Infof("Allowing owned pod %s/%s to pass through webhook", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
return nil, nil
}
controller, err = kube.NewGenericResourceFromPod(pod, pod)
originalObject = pod
} else {
controller, err = kube.NewGenericResourceFromBytes(req.Object.Raw)
pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw)
}
controller, err := kube.NewGenericResourceFromPod(pod, originalObject)
if err != nil {
return nil, err
}
// TODO: consider enabling multi-resource checks
controllerResult, err := validator.ApplyAllSchemaChecks(&v.Config, nil, controller)
controller.Kind = req.AdmissionRequest.Kind.Kind
var controllerResult validator.Result
controllerResult, err = validator.ApplyAllSchemaChecks(&v.Config, controller)
if err != nil {
return nil, err
}
return &controllerResult, nil
return controllerResult.PodResult, nil
}
// Handle for Validator to run validation checks.
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
logrus.Info("Starting request")
result, err := v.handleInternal(req)
podResult, err := v.handleInternal(req)
if err != nil {
logrus.Errorf("Error validating request: %v", err)
return admission.Errored(http.StatusBadRequest, err)
}
allowed := true
reason := ""
if result != nil {
numDangers := result.GetSummary().Dangers
if podResult != nil {
numDangers := podResult.GetSummary().Dangers
if numDangers > 0 {
allowed = false
reason = getFailureReason(*result)
reason = getFailureReason(*podResult)
}
logrus.Infof("%d validation errors found when validating %s", numDangers, result.Name)
logrus.Infof("%d validation errors found when validating %s", numDangers, podResult.Name)
}
return admission.ValidationResponse(allowed, reason)
}
func getFailureReason(result validator.Result) string {
func getFailureReason(podResult validator.PodResult) string {
reason := "\nPolaris prevented this deployment due to configuration problems:\n"
for _, message := range result.Results {
for _, message := range podResult.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason += fmt.Sprintf("- %s: %s\n", result.Kind, message.Message)
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
}
}
podResult := result.PodResult
if podResult != nil {
for _, message := range podResult.Results {
for _, containerResult := range podResult.ContainerResults {
for _, message := range containerResult.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
}
}
for _, containerResult := range podResult.ContainerResults {
for _, message := range containerResult.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
}
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
}
}
}
-13
View File
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: 128Mi
-14
View File
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: 256Mi
cpu: 100m
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: 128Mi
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: 256Mi
cpu: 100m
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
capabilities:
add: ["ALL"]
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
capabilities:
add:
- NET_ADMIN
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
capabilities:
add:
-11
View File
@@ -1,11 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
hostIPC: true
containers:
- name: nginx
image: nginx
-12
View File
@@ -1,12 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
hostIPC: false
containers:
- name: nginx
image: nginx
-11
View File
@@ -1,11 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
hostNetwork: true
containers:
- name: nginx
image: nginx
-11
View File
@@ -1,11 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
hostNetwork: false
containers:
- name: nginx
image: nginx
-11
View File
@@ -1,11 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
hostPID: true
containers:
- name: nginx
image: nginx
-11
View File
@@ -1,11 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
hostPID: false
containers:
- name: nginx
image: nginx
-13
View File
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
hostPort: 8080
-12
View File
@@ -1,12 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
@@ -1,15 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
capabilities:
drop:
- ALL
containers:
- name: nginx
image: nginx
@@ -1,31 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
capabilities:
drop:
containers:
- name: nginx
image: nginx
securityContext:
capabilities:
drop:
- NET_ADMIN
- CHOWN
- DAC_OVERRIDE
- FSETID
- FOWNER
- MKNOD
- NET_RAW
- SETGID
- SETUID
- SETFCAP
- SETPCAP
- NET_BIND_SERVICE
- SYS_CHROOT
- KILL
- AUDIT_WRITE
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: k8s.gcr.io/busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600.
@@ -1,21 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: k8s.gcr.io/busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
@@ -1,12 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "512Mi"
@@ -1,12 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: "128Mi"
@@ -1,8 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
@@ -1,10 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: not-nginx
spec:
containers:
- name: nginx
image: nginx
@@ -1,10 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
@@ -1,22 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: zookeeper
labels:
app: zookeeper
spec:
template:
spec:
containers:
- name: zookeeper
image: zookeeper
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: asdfasf
@@ -1,12 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: zookeeper
labels:
app: zookeeper
spec:
template:
spec:
containers:
- name: zookeeper
image: zookeeper
@@ -1,42 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: zookeeper
labels:
app.kubernetes.io/name: zookeeper
spec:
template:
spec:
containers:
- name: zookeeper
image: zookeeper
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zookeeper-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: zookeeper
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: abcd-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: abcd
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: fghi-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: fghi
@@ -1,22 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: zookeeper
labels:
app.kubernetes.io/name: zookeeper
spec:
template:
spec:
containers:
- name: zookeeper
image: zookeeper
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zookeeper-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: zookeeper
@@ -1,11 +0,0 @@
apiVersion: v1
kind: Deployment
metadata:
name: nginx
labels:
env: test
spec:
replicas: 1
template:
spec:
containers: []
@@ -1,12 +0,0 @@
apiVersion: v1
kind: Deployment
metadata:
name: nginx
labels:
env: test
spec:
replicas: 3
template:
spec:
containers: []
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
readOnlyRootFilesystem: false
containers:
- securityContext:
readOnlyRootFilesystem: true
- securityContext:
readOnlyRootFilesystem: false
@@ -1,15 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
readOnlyRootFilesystem: false
containers:
- name: container-a
securityContext:
readOnlyRootFilesystem: true
- name: container-b
@@ -1,10 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- securityContext:
readOnlyRootFilesystem: false
@@ -1,12 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
readOnlyRootFilesystem: false
containers:
- securityContext:
readOnlyRootFilesystem: true
@@ -1,10 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- securityContext:
readOnlyRootFilesystem: true
@@ -1,8 +0,0 @@
apiVersion: v1
kind: PodDisruptionBudget
metadata:
name: nginx
labels:
env: test
status:
disruptionsAllowed: 0
@@ -1,8 +0,0 @@
apiVersion: v1
kind: PodDisruptionBudget
metadata:
name: nginx
labels:
env: test
status:
disruptionsAllowed: 1
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
allowPrivilegeEscalation: false
containers:
- name: nginx
image: nginx
securityContext:
allowPrivilegeEscalation: true
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
allowPrivilegeEscalation: false
containers:
- name: nginx
image: nginx
securityContext:
@@ -1,14 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
securityContext:
allowPrivilegeEscalation: true
containers:
- name: nginx
image: nginx
securityContext:
allowPrivilegeEscalation: false
@@ -1,10 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
@@ -1,12 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: Always

Some files were not shown because too many files have changed in this diff Show More