mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1d4cd952 | ||
|
|
c398f1043c | ||
|
|
091fa77d11 | ||
|
|
1b97f31edb | ||
|
|
8c454fa733 | ||
|
|
32c1150b28 | ||
|
|
19341205b7 | ||
|
|
89dfce5bbd | ||
|
|
a62389b85f | ||
|
|
5aa397ca22 | ||
|
|
d41ce88f16 | ||
|
|
310015ff53 | ||
|
|
bb7ba83362 | ||
|
|
c0d8eb6318 | ||
|
|
19bf91e13b | ||
|
|
9ae4f774e9 | ||
|
|
6c14d968c9 | ||
|
|
3e655d846b | ||
|
|
0232e31a50 | ||
|
|
a396f049d0 | ||
|
|
a55acdd372 | ||
|
|
ca3e46ebbd | ||
|
|
e661e9542d | ||
|
|
b923caf79e | ||
|
|
3e79863aa7 | ||
|
|
d172d8e18b | ||
|
|
38c410c5aa | ||
|
|
653eac6002 | ||
|
|
6c995eb04c | ||
|
|
e52f1e76e9 | ||
|
|
cf69099d3a | ||
|
|
20f1be3975 | ||
|
|
f602687c90 | ||
|
|
a3404a132d | ||
|
|
46836ccc4c | ||
|
|
ff8e66af07 | ||
|
|
01cc5fa642 | ||
|
|
cd47487b0b | ||
|
|
7e7e553c0d | ||
|
|
8385fd10e5 | ||
|
|
ccc795d58e | ||
|
|
0007b12080 | ||
|
|
2958b5cc07 | ||
|
|
0c5014489e | ||
|
|
ebf99869ff | ||
|
|
a43a0fe2f5 | ||
|
|
88b57f9f32 | ||
|
|
e028c34448 | ||
|
|
7c1ec6a762 | ||
|
|
fa6843ae61 | ||
|
|
76cc9e3296 | ||
|
|
6bcdb834ed | ||
|
|
2bac1c9ba9 | ||
|
|
0e6ca81f57 | ||
|
|
4bc2522e25 | ||
|
|
d4bb6239c7 | ||
|
|
082e9c0406 | ||
|
|
178742e0f3 | ||
|
|
1651aa7004 |
@@ -87,8 +87,8 @@ references:
|
||||
run:
|
||||
name: Install GoReleaser
|
||||
command: |
|
||||
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.131.1/goreleaser_amd64.deb
|
||||
echo "640790dcbfa864f26de4c26c2d491f293a64525c8c6641c5bbdec7136b38977e goreleaser.deb" | sha256sum -c -
|
||||
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.174.2/goreleaser_amd64.deb
|
||||
echo "bad33997ea9977a84196bdca1d5993fada909cd81c3e88d52bd297666bea61a4 goreleaser.deb" | sha256sum -c -
|
||||
sudo dpkg -i goreleaser.deb
|
||||
rm goreleaser.deb
|
||||
|
||||
|
||||
+10
-3
@@ -7,21 +7,28 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
builds:
|
||||
- id: watcher
|
||||
- id: polaris
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- 386
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
archives:
|
||||
- id: polaris
|
||||
builds: ["polaris"]
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
||||
brews:
|
||||
- name: polaris
|
||||
github:
|
||||
tap:
|
||||
owner: FairwindsOps
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
* @rbren @makoscafee @baderbuddy
|
||||
* @rbren @makoscafee
|
||||
+1
-1
@@ -15,7 +15,7 @@ RUN go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
COPY . .
|
||||
RUN packr2 build -a -o polaris *.go
|
||||
|
||||
FROM alpine:3.13
|
||||
FROM alpine:3.14
|
||||
WORKDIR /usr/local/bin
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<br>
|
||||
<h3>Best Practices for Kubernetes Workload Configuration</h3>
|
||||
<a href="https://github.com/FairwindsOps/polaris">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=4.0.1&color=239922">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=4.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.svg">
|
||||
<a href="https://circleci.com/gh/FairwindsOps/polaris">
|
||||
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
|
||||
</a>
|
||||
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
|
||||
@@ -32,10 +32,18 @@ Polaris can be run in three different modes:
|
||||
## Documentation
|
||||
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
|
||||
|
||||
<!-- Begin boilerplate -->
|
||||
## Join the Fairwinds Open Source Community
|
||||
|
||||
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap, and network with fellow Kubernetes users. [Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g) or [join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
|
||||
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap,
|
||||
and network with fellow Kubernetes users.
|
||||
[Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)
|
||||
or
|
||||
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
|
||||
|
||||
<a href="https://www.fairwinds.com/t-shirt-offer?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris-tshirt">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png" alt="Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt!" />
|
||||
</a>
|
||||
|
||||
## Other Projects from Fairwinds
|
||||
|
||||
@@ -45,12 +53,14 @@ Enjoying Polaris? Check out some of our other projects:
|
||||
* [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available
|
||||
* [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters
|
||||
|
||||
Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris)
|
||||
## Fairwinds Insights
|
||||
<p align="center">
|
||||
<img src="https://polaris.docs.fairwinds.com/img/FW_Insights_Polaris.svg" alt="Fairwinds Insights" width="550"/>
|
||||
</p>
|
||||
|
||||
If you're interested in running Polaris in multiple clusters,
|
||||
tracking the results over time, integrating with Slack, Datadog, and Jira,
|
||||
or unlocking other functionality, check out
|
||||
[Fairwinds Insights](https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris), a platform for auditing and enforcing policy in Kubernetes clusters.
|
||||
[Fairwinds Insights](https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris),
|
||||
a platform for auditing and enforcing policy in Kubernetes clusters.
|
||||
|
||||
<a href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_Polaris_Ad.png" alt="Fairwinds Insights" />
|
||||
</a>
|
||||
|
||||
@@ -17,10 +17,10 @@ schema:
|
||||
allOf:
|
||||
- not:
|
||||
contains:
|
||||
const: ALL
|
||||
pattern: '^(?i)ALL$'
|
||||
- not:
|
||||
contains:
|
||||
const: SYS_ADMIN
|
||||
pattern: '^(?i)SYS_ADMIN$'
|
||||
- not:
|
||||
contains:
|
||||
const: NET_ADMIN
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
|
||||
@@ -22,35 +22,35 @@ schema:
|
||||
type: array
|
||||
oneOf:
|
||||
- contains:
|
||||
const: ALL
|
||||
pattern: '^(?i)ALL$'
|
||||
- allOf:
|
||||
- contains:
|
||||
const: NET_ADMIN
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
- contains:
|
||||
const: CHOWN
|
||||
pattern: '^(?i)CHOWN$'
|
||||
- contains:
|
||||
const: DAC_OVERRIDE
|
||||
pattern: '^(?i)DAC_OVERRIDE$'
|
||||
- contains:
|
||||
const: FSETID
|
||||
pattern: '^(?i)FSETID$'
|
||||
- contains:
|
||||
const: FOWNER
|
||||
pattern: '^(?i)FOWNER$'
|
||||
- contains:
|
||||
const: MKNOD
|
||||
pattern: '^(?i)MKNOD$'
|
||||
- contains:
|
||||
const: NET_RAW
|
||||
pattern: '^(?i)NET_RAW$'
|
||||
- contains:
|
||||
const: SETGID
|
||||
pattern: '^(?i)SETGID$'
|
||||
- contains:
|
||||
const: SETUID
|
||||
pattern: '^(?i)SETUID$'
|
||||
- contains:
|
||||
const: SETFCAP
|
||||
pattern: '^(?i)SETFCAP$'
|
||||
- contains:
|
||||
const: SETPCAP
|
||||
pattern: '^(?i)SETPCAP$'
|
||||
- contains:
|
||||
const: NET_BIND_SERVICE
|
||||
pattern: '^(?i)NET_BIND_SERVICE$'
|
||||
- contains:
|
||||
const: SYS_CHROOT
|
||||
pattern: '^(?i)SYS_CHROOT$'
|
||||
- contains:
|
||||
const: KILL
|
||||
pattern: '^(?i)KILL$'
|
||||
- contains:
|
||||
const: AUDIT_WRITE
|
||||
pattern: '^(?i)AUDIT_WRITE$'
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
successMessage: disruptionsAllowed is greater than zero
|
||||
failureMessage: disruptionsAllowed is not greater than zero
|
||||
successMessage: Voluntary evictions are possible
|
||||
failureMessage: Voluntary evictions are not possible
|
||||
category: Reliability
|
||||
target: policy/PodDisruptionBudget
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- spec
|
||||
properties:
|
||||
status:
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- disruptionsAllowed
|
||||
properties:
|
||||
disruptionsAllowed:
|
||||
type: integer
|
||||
minimum: 1
|
||||
minAvailable:
|
||||
not:
|
||||
const: '100%'
|
||||
maxUnavailable:
|
||||
allOf:
|
||||
- not:
|
||||
const: 0
|
||||
- not:
|
||||
const: '0%'
|
||||
|
||||
+63
-16
@@ -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,6 +39,8 @@ var auditOutputFile string
|
||||
var auditOutputFormat string
|
||||
var resourceToAudit string
|
||||
var useColor bool
|
||||
var helmChart string
|
||||
var helmValues string
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(auditCmd)
|
||||
@@ -52,6 +54,8 @@ 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{
|
||||
@@ -62,8 +66,28 @@ var auditCmd = &cobra.Command{
|
||||
if displayName != "" {
|
||||
config.DisplayName = displayName
|
||||
}
|
||||
if helmChart != "" {
|
||||
var err error
|
||||
auditPath, err = ProcessHelmTemplates(helmChart, helmValues)
|
||||
if err != nil {
|
||||
logrus.Infof("Couldn't process helm chart: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat, useColor)
|
||||
k, err := kube.CreateResourceProvider(context.TODO(), auditPath, resourceToAudit, config)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
auditData, err := validator.RunAudit(config, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -77,26 +101,50 @@ var auditCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string, useColor bool) validator.AuditData {
|
||||
// Create a kubernetes client resource provider
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, workload, c)
|
||||
// ProcessHelmTemplates turns helm into yaml to be processed by Polaris or the other tools.
|
||||
func ProcessHelmTemplates(helmChart, helmValues string) (string, error) {
|
||||
cmd := exec.Command("helm", "dependency", "update", helmChart)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(c, k, onlyShowFailedTests)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params := []string{
|
||||
"template", helmChart,
|
||||
helmChart,
|
||||
"--output-dir",
|
||||
dir,
|
||||
}
|
||||
if helmValues != "" {
|
||||
params = append(params, "--values", helmValues)
|
||||
}
|
||||
|
||||
cmd = exec.Command("helm", params...)
|
||||
output, err = cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
logrus.Error(string(output))
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFormat string, useColor bool, onlyShowFailedTests bool) {
|
||||
if onlyShowFailedTests {
|
||||
auditData = auditData.RemoveSuccessfulResults()
|
||||
}
|
||||
var outputBytes []byte
|
||||
var err error
|
||||
if outputFormat == "score" {
|
||||
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
|
||||
} else if outputFormat == "yaml" {
|
||||
jsonBytes, err := json.Marshal(auditData)
|
||||
var jsonBytes []byte
|
||||
jsonBytes, err = json.Marshal(auditData)
|
||||
if err == nil {
|
||||
outputBytes, err = yaml.JSONToYAML(jsonBytes)
|
||||
}
|
||||
@@ -155,5 +203,4 @@ func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, wor
|
||||
}
|
||||
}
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
+7
-6
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var configPath string
|
||||
var disallowExemptions bool
|
||||
var disallowExemptions, disallowConfigExemptions, disallowAnnotationExemptions bool
|
||||
var logLevel string
|
||||
var auditPath string
|
||||
var displayName string
|
||||
@@ -37,7 +37,9 @@ var (
|
||||
func init() {
|
||||
// Flags
|
||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Location of Polaris configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any exemptions from configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any configured exemption.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowConfigExemptions, "disallow-config-exemptions", "", false, "Disallow exemptions set within the configuration file.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disallowAnnotationExemptions, "disallow-annotation-exemptions", "", false, "Disallow any exemption defined as a controller annotation.")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level.")
|
||||
flag.Parse()
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
@@ -63,10 +65,9 @@ var rootCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if disallowExemptions {
|
||||
config.DisallowExemptions = true
|
||||
}
|
||||
|
||||
config.DisallowExemptions = disallowExemptions
|
||||
config.DisallowConfigExemptions = disallowConfigExemptions
|
||||
config.DisallowAnnotationExemptions = disallowAnnotationExemptions
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Error("You must specify a sub-command.")
|
||||
|
||||
@@ -31,12 +31,12 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
@@ -119,7 +119,7 @@ spec:
|
||||
- dashboard
|
||||
- --port
|
||||
- "8080"
|
||||
image: 'quay.io/fairwinds/polaris:4.0'
|
||||
image: 'quay.io/fairwinds/polaris:4.2'
|
||||
imagePullPolicy: 'Always'
|
||||
name: dashboard
|
||||
ports:
|
||||
|
||||
+4
-4
@@ -31,12 +31,12 @@ rules:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- apiGroups:
|
||||
- 'monitoring.coreos.com'
|
||||
resources:
|
||||
resources:
|
||||
- 'prometheuses'
|
||||
- 'alertmanagers'
|
||||
verbs:
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
@@ -117,7 +117,7 @@ spec:
|
||||
command:
|
||||
- polaris
|
||||
- webhook
|
||||
image: 'quay.io/fairwinds/polaris:4.0'
|
||||
image: 'quay.io/fairwinds/polaris:4.2'
|
||||
imagePullPolicy: 'Always'
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -23,6 +23,13 @@
|
||||
color: $successColor;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.2rem solid $warningColor;
|
||||
}
|
||||
blockquote p {
|
||||
color: $warningColor;
|
||||
}
|
||||
|
||||
.theme-default-content:not(.custom),
|
||||
.page-nav,
|
||||
.page-edit,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation: Polaris can be run as an admission controller that acts as a validating webhook."
|
||||
---
|
||||
# Admission Controller
|
||||
> Want to manage the Admission Controller across multiple clusters? Check out
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
|
||||
@@ -1,7 +1,49 @@
|
||||
---
|
||||
sidebarDepth: 0
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Changelog"
|
||||
|
||||
---
|
||||
|
||||
## 4.2.0
|
||||
* New flags `--disallow-(config|annotation)-exemptions`
|
||||
* Kubernetes dependency updates
|
||||
* Documentation updates
|
||||
## 4.1.0
|
||||
* Handle case-insentitivity in capabilities checks
|
||||
* Change test for PDB disruptions to better handle IaC
|
||||
|
||||
## 4.0.9
|
||||
* Update Alpine base image
|
||||
|
||||
## 4.0.8
|
||||
* Fix support for namespace checks
|
||||
|
||||
## 4.0.7
|
||||
* Fix goreleaser format
|
||||
|
||||
## 4.0.6
|
||||
* Change goreleaser format
|
||||
* Fix `--helm-values` flag
|
||||
|
||||
## 4.0.5
|
||||
* Bugfix for repeated objects on the dashboard
|
||||
|
||||
## 4.0.4
|
||||
* Bugfix for validating webhook and non-pod checks
|
||||
|
||||
## 4.0.3
|
||||
* Fixed bad interaction between `--set-exit-score-below` and `--only-show-failed-tests`
|
||||
* Dependency updates
|
||||
* Support for Helm chart scanning
|
||||
|
||||
## 4.0.2
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.1
|
||||
* Goreleaser fix
|
||||
|
||||
## 4.0.0
|
||||
* Add support for arbitrary resources, like Ingress or PodDisruptionBudget
|
||||
* Add support check templating (see docs)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ensure that CPU and memory settings are configured, so that Kubernetes can schedule your workload effectively"
|
||||
---
|
||||
# Efficiency
|
||||
|
||||
These checks ensure that CPU and memory settings are configured, so that
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Make sure your Kubernetes workloads are always available, and are running the correct image."
|
||||
---
|
||||
# Reliability
|
||||
|
||||
These checks help to make sure your workloads are always available,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Security Checks Documentation"
|
||||
---
|
||||
# Security
|
||||
|
||||
These checks are related to security concerns. Workloads that fail these
|
||||
|
||||
+13
-5
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Learn your CLI options"
|
||||
---
|
||||
#### CLI Options
|
||||
|
||||
```
|
||||
@@ -14,10 +19,12 @@ webhook
|
||||
Runs the webhook webserver
|
||||
|
||||
# global flags
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
--disallow-exemptions Disallow any exemptions from configuration file.
|
||||
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string Logrus log level. (default "info")
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
--disallow-exemptions Disallow any exemptions from configuration file.
|
||||
--disallow-config-exemptions Disallow exemptions set within the configuration file.
|
||||
--disallow-annotation-exemptions Disallow any exemption defined as a controller annotation.
|
||||
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string Logrus log level. (default "info")
|
||||
|
||||
# dashboard flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
@@ -33,6 +40,8 @@ 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.
|
||||
@@ -41,7 +50,6 @@ webhook
|
||||
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
|
||||
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
|
||||
|
||||
|
||||
# webhook flags
|
||||
--disable-webhook-config-installer disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.
|
||||
-h, --help help for webhook
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Code of Conduct"
|
||||
---
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Contribution Guidelines"
|
||||
---
|
||||
# Contributing
|
||||
|
||||
Issues, whether bugs, tasks, or feature requests are essential for keeping Polaris great. We believe it should be as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can keep on top of things.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation on setting checks by severity "
|
||||
---
|
||||
# Check Settings
|
||||
Each check can be assigned a `severity`. Only checks with a severity of `danger` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `danger` will result in a change being rejected.
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Learn about Polaris defaults and how to customize configurations. "
|
||||
---
|
||||
# Configuration
|
||||
|
||||
The default Polaris configuration can be [seen here](https://github.com/FairwindsOps/polaris/blob/master/examples/config.yaml).
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation: Create your own checks, you can use JSON Schema"
|
||||
---
|
||||
# Custom Checks
|
||||
|
||||
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/).
|
||||
@@ -93,6 +98,19 @@ customChecks:
|
||||
resourceMaximum: "2"
|
||||
```
|
||||
|
||||
## Resource Presence
|
||||
You can test for the presence of a resource in each Namespace. For example, to
|
||||
ensure an AlertmanagerConfig is in every Namespace:
|
||||
```yaml
|
||||
successMessage: Namespace has monitoring configuration
|
||||
failureMessage: Namespace should have monitoring configuration
|
||||
category: Security
|
||||
target: Namespace
|
||||
schema: {}
|
||||
additionalSchemas:
|
||||
monitoring.coreos.com/AlertmanagerConfig: {}
|
||||
```
|
||||
|
||||
## Templating
|
||||
You can also utilize go templating in your JSON schema in order to match one field against another.
|
||||
E.g. here is the built-in check to ensure that the `name` annotation matches the object's name:
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Exemptions"
|
||||
---
|
||||
# Exemptions
|
||||
Sometimes a workload really does need to do things that Polaris considers insecure. For instance,
|
||||
many of the `kube-system` workloads need to run as root, or need access to the host network. In these
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Ways to run Polaris | The Polaris dashboard can be installed on a cluster using kubectl or Helm"
|
||||
---
|
||||
# Dashboard
|
||||
|
||||
> Want to see Polaris results for all your clusters in a single dashboard? Check out
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation | Polaris can be used on the command line to audit local Kubernetes manifests stored in YAML files."
|
||||
---
|
||||
# Infrastructure as Code
|
||||
> Want to see results for all your IaC repos in one place? Check out
|
||||
> [Fairwinds Insights](https://www.fairwinds.com/fairwinds-polaris-upgrade)
|
||||
@@ -55,6 +60,14 @@ polaris audit --audit-path ./deploy/ \
|
||||
--only-show-failed-tests true
|
||||
```
|
||||
|
||||
### Audit Helm Charts
|
||||
You can audit helm charts using the `--helm-chart` and `--helm-values` flags:
|
||||
```
|
||||
polaris audit \
|
||||
--helm-chart ./deploy/chart \
|
||||
--helm-values ./deploy/chart/values.yml
|
||||
```
|
||||
|
||||
### As Github Action
|
||||
#### Setup polaris action
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
meta:
|
||||
- name: description
|
||||
content: "Fairwinds Polaris | Documentation"
|
||||
---
|
||||
Generated
+16613
-1853
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,7 @@ module github.com/fairwindsops/polaris
|
||||
go 1.15
|
||||
|
||||
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.13.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
|
||||
@@ -15,14 +12,14 @@ require (
|
||||
github.com/qri-io/jsonschema v0.1.1
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/thoas/go-funk v0.8.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
k8s.io/api v0.21.1
|
||||
k8s.io/apimachinery v0.21.1
|
||||
k8s.io/client-go v0.21.1
|
||||
sigs.k8s.io/controller-runtime v0.9.0-alpha.1
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
github.com/thoas/go-funk v0.9.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
sigs.k8s.io/controller-runtime v0.10.1
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/cmd/polaris"
|
||||
cmd "github.com/fairwindsops/polaris/cmd/polaris"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "4.0.1"
|
||||
Version = "4.2.0"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -29,11 +29,13 @@ import (
|
||||
|
||||
// Configuration contains all of the config for the validation checks.
|
||||
type Configuration struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Checks map[string]Severity `json:"checks"`
|
||||
CustomChecks map[string]SchemaCheck `json:"customChecks"`
|
||||
Exemptions []Exemption `json:"exemptions"`
|
||||
DisallowExemptions bool `json:"disallowExemptions"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Checks map[string]Severity `json:"checks"`
|
||||
CustomChecks map[string]SchemaCheck `json:"customChecks"`
|
||||
Exemptions []Exemption `json:"exemptions"`
|
||||
DisallowExemptions bool `json:"disallowExemptions"`
|
||||
DisallowConfigExemptions bool `json:"disallowConfigExemptions"`
|
||||
DisallowAnnotationExemptions bool `json:"disallowAnnotationExemptions"`
|
||||
}
|
||||
|
||||
// Exemption represents an exemption to normal rules
|
||||
|
||||
@@ -11,7 +11,7 @@ func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, con
|
||||
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
|
||||
return false
|
||||
}
|
||||
if conf.DisallowExemptions {
|
||||
if conf.DisallowExemptions || conf.DisallowConfigExemptions {
|
||||
return true
|
||||
}
|
||||
for _, exemption := range conf.Exemptions {
|
||||
|
||||
@@ -167,6 +167,9 @@ func (check *SchemaCheck) Initialize(id string) error {
|
||||
}
|
||||
check.SchemaString = string(jsonBytes)
|
||||
}
|
||||
if check.AdditionalSchemaStrings == nil {
|
||||
check.AdditionalSchemaStrings = make(map[string]string)
|
||||
}
|
||||
for kind, schema := range check.AdditionalSchemas {
|
||||
jsonBytes, err := json.Marshal(schema)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
padding-right: 40px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights img {
|
||||
max-width: 400px;
|
||||
.card.transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights a {
|
||||
background-color: #20162D;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 66 KiB |
@@ -130,13 +130,17 @@ func writeTemplate(tmpl *template.Template, data *templateData, w http.ResponseW
|
||||
|
||||
func getConfigForQuery(base config.Configuration, query url.Values) config.Configuration {
|
||||
c := base
|
||||
exemptions := query.Get("disallowExemptions")
|
||||
if exemptions == "false" {
|
||||
c.DisallowExemptions = false
|
||||
}
|
||||
if exemptions == "true" {
|
||||
switch query.Get("disallowExemptions") {
|
||||
case "true":
|
||||
c.DisallowExemptions = true
|
||||
c.DisallowConfigExemptions = true
|
||||
c.DisallowAnnotationExemptions = true
|
||||
default:
|
||||
c.DisallowExemptions = false
|
||||
c.DisallowConfigExemptions = false
|
||||
c.DisallowAnnotationExemptions = false
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -181,7 +185,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
}
|
||||
|
||||
var auditDataObj validator.AuditData
|
||||
auditDataObj, err = validator.RunAudit(adjustedConf, k, false)
|
||||
auditDataObj, err = validator.RunAudit(adjustedConf, k)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -214,7 +218,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
}
|
||||
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(adjustedConf, k, false)
|
||||
auditData, err = validator.RunAudit(adjustedConf, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting audit data: %v", err)
|
||||
http.Error(w, "Error running audit", 500)
|
||||
|
||||
@@ -83,10 +83,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="insights" class="card insights py-2">
|
||||
<img class="align-self-center" src="static/images/FW_Insights_Polaris.svg" />
|
||||
<span>Fairwinds, the company behind Polaris, now offers Fairwinds Insights, a multi-cluster, multi-user Kubernetes configuration validation and policy enforcement platform. Fairwinds Insights can help you avoid errors that lead to wasted time, compute costs, and increased risk.</span>
|
||||
<a class="col-auto py-1 px-3 mt-2 align-self-center" href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">Try Insights</a>
|
||||
<div class="card transparent">
|
||||
<a class="align-self-center"
|
||||
target="_blank"
|
||||
href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="static/images/FW_Insights_Polaris.svg" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="categories" class="card category">
|
||||
@@ -134,7 +136,11 @@
|
||||
|
||||
{{ range $namespace, $results := .FilteredAuditData.GetResultsByNamespace }}
|
||||
<div id="{{ $namespace }}" class="card namespace">
|
||||
{{ if eq $namespace "" }}
|
||||
<h3>Cluster Resources</h3>
|
||||
{{ else }}
|
||||
<h3>Namespace: <strong>{{ $namespace }}</strong></h3>
|
||||
{{ end }}
|
||||
<div class="expandable-table">
|
||||
{{ range $index, $result := $results }}
|
||||
<div class="resource-info">
|
||||
|
||||
@@ -25,24 +25,23 @@ 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 {
|
||||
@@ -61,7 +60,6 @@ func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (Generi
|
||||
}
|
||||
workload.PodSpec = &podSpec
|
||||
}
|
||||
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
@@ -101,7 +99,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
|
||||
@@ -161,7 +159,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 {
|
||||
|
||||
@@ -168,7 +168,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
|
||||
@@ -277,8 +277,11 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
for key := range check.AdditionalSchemas {
|
||||
neededKinds = append(neededKinds, conf.TargetKind(key))
|
||||
}
|
||||
for key := range check.AdditionalSchemaStrings {
|
||||
neededKinds = append(neededKinds, conf.TargetKind(key))
|
||||
}
|
||||
for _, kind := range neededKinds {
|
||||
if !funk.Contains(conf.HandledTargets, kind) {
|
||||
if !funk.Contains(conf.HandledTargets, kind) && !funk.Contains(additionalKinds, kind) {
|
||||
additionalKinds = append(additionalKinds, kind)
|
||||
}
|
||||
}
|
||||
@@ -298,7 +301,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
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestValidatePDB(t *testing.T) {
|
||||
},
|
||||
}
|
||||
pdb := unstructured.Unstructured{}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&pdb)
|
||||
res, err := kube.NewGenericResourceFromUnstructured(pdb)
|
||||
res.Kind = "PodDisruptionBudget"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
@@ -45,7 +45,7 @@ func TestValidatePDB(t *testing.T) {
|
||||
assert.False(t, results.Success)
|
||||
assert.Equal(t, conf.SeverityWarning, results.Severity)
|
||||
assert.Equal(t, "Reliability", results.Category)
|
||||
assert.EqualValues(t, "disruptionsAllowed is not greater than zero", results.Message)
|
||||
assert.EqualValues(t, "Voluntary evictions are not possible", results.Message)
|
||||
}
|
||||
|
||||
func TestValidateIngress(t *testing.T) {
|
||||
@@ -70,7 +70,7 @@ func TestValidateIngress(t *testing.T) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&unst)
|
||||
res, err := kube.NewGenericResourceFromUnstructured(unst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// RunAudit runs a full Polaris audit and returns an AuditData object
|
||||
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, onlyShowFailedTests bool) (AuditData, error) {
|
||||
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
|
||||
displayName := config.DisplayName
|
||||
if displayName == "" {
|
||||
displayName = kubeResources.SourceName
|
||||
@@ -42,9 +42,6 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, o
|
||||
Results: results,
|
||||
}
|
||||
auditData.Score = auditData.GetSummary().GetScore()
|
||||
if onlyShowFailedTests {
|
||||
auditData.RemoveSuccessfulResults()
|
||||
}
|
||||
return auditData, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestGetTemplateData(t *testing.T) {
|
||||
score := uint(0)
|
||||
|
||||
var actualAudit AuditData
|
||||
actualAudit, err = RunAudit(c, resources, false)
|
||||
actualAudit, err = RunAudit(c, resources)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, score, actualAudit.Score, "")
|
||||
assert.EqualValues(t, sum, actualAudit.GetSummary())
|
||||
|
||||
+36
-21
@@ -19,6 +19,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/thoas/go-funk"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
)
|
||||
@@ -52,10 +53,12 @@ type AuditData struct {
|
||||
}
|
||||
|
||||
// RemoveSuccessfulResults remove all test that have passed.
|
||||
func (res *AuditData) RemoveSuccessfulResults() {
|
||||
for _, auditDataResult := range res.Results {
|
||||
auditDataResult.removeSuccessfulResults()
|
||||
}
|
||||
func (res AuditData) RemoveSuccessfulResults() AuditData {
|
||||
resCopy := res
|
||||
resCopy.Results = funk.Map(res.Results, func(auditDataResult Result) Result {
|
||||
return auditDataResult.removeSuccessfulResults()
|
||||
}).([]Result)
|
||||
return resCopy
|
||||
}
|
||||
|
||||
// ClusterInfo contains Polaris results as well as some high-level stats
|
||||
@@ -80,12 +83,14 @@ type ResultMessage struct {
|
||||
// ResultSet contiains the results for a set of checks
|
||||
type ResultSet map[string]ResultMessage
|
||||
|
||||
func (res ResultSet) removeSuccessfulResults() {
|
||||
func (res ResultSet) removeSuccessfulResults() ResultSet {
|
||||
newResults := ResultSet{}
|
||||
for k, resultMessage := range res {
|
||||
if resultMessage.Success {
|
||||
delete(res, k)
|
||||
if !resultMessage.Success {
|
||||
newResults[k] = resultMessage
|
||||
}
|
||||
}
|
||||
return newResults
|
||||
}
|
||||
|
||||
// Result provides results for a Kubernetes object
|
||||
@@ -98,9 +103,14 @@ type Result struct {
|
||||
CreatedTime time.Time
|
||||
}
|
||||
|
||||
func (res *Result) removeSuccessfulResults() {
|
||||
res.Results.removeSuccessfulResults()
|
||||
res.PodResult.removeSuccessfulResults()
|
||||
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
|
||||
}
|
||||
|
||||
// PodResult provides a list of validation messages for each pod.
|
||||
@@ -110,14 +120,13 @@ type PodResult struct {
|
||||
ContainerResults []ContainerResult
|
||||
}
|
||||
|
||||
func (res *PodResult) removeSuccessfulResults() {
|
||||
if res == nil {
|
||||
return
|
||||
}
|
||||
res.Results.removeSuccessfulResults()
|
||||
for _, containerResult := range res.ContainerResults {
|
||||
containerResult.removeSuccessfulResults()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// ContainerResult provides a list of validation messages for each container.
|
||||
@@ -126,8 +135,10 @@ type ContainerResult struct {
|
||||
Results ResultSet
|
||||
}
|
||||
|
||||
func (res *ContainerResult) removeSuccessfulResults() {
|
||||
res.Results.removeSuccessfulResults()
|
||||
func (res ContainerResult) removeSuccessfulResults() ContainerResult {
|
||||
resCopy := res
|
||||
resCopy.Results = res.Results.removeSuccessfulResults()
|
||||
return resCopy
|
||||
}
|
||||
|
||||
func fillString(id string, l int) string {
|
||||
@@ -153,7 +164,11 @@ func (res AuditData) GetPrettyOutput(useColor bool) string {
|
||||
|
||||
// GetPrettyOutput returns a human-readable string
|
||||
func (res Result) GetPrettyOutput() string {
|
||||
str := titleColor.Sprint(fmt.Sprintf("%s %s in namespace %s\n", res.Kind, res.Name, res.Namespace))
|
||||
str := titleColor.Sprint(fmt.Sprintf("%s %s", res.Kind, res.Name))
|
||||
if res.Namespace != "" {
|
||||
str += titleColor.Sprint(fmt.Sprintf("in namespace %s", res.Namespace))
|
||||
}
|
||||
str += "\n"
|
||||
str += res.Results.GetPrettyOutput()
|
||||
if res.PodResult != nil {
|
||||
str += res.PodResult.GetPrettyOutput()
|
||||
|
||||
+10
-1
@@ -23,7 +23,9 @@ type schemaTestCase struct {
|
||||
}
|
||||
|
||||
func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) {
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(test.Resource.ObjectMeta, checkID) {
|
||||
if !conf.DisallowExemptions &&
|
||||
!conf.DisallowAnnotationExemptions &&
|
||||
hasExemptionAnnotation(test.Resource.ObjectMeta, checkID) {
|
||||
return nil, nil
|
||||
}
|
||||
check, ok := conf.CustomChecks[checkID]
|
||||
@@ -261,6 +263,13 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
|
||||
break
|
||||
}
|
||||
resources := test.ResourceProvider.Resources[groupkind]
|
||||
namespace := test.Resource.ObjectMeta.GetNamespace()
|
||||
if test.Resource.Kind == "Namespace" {
|
||||
namespace = test.Resource.ObjectMeta.GetName()
|
||||
}
|
||||
resources = funk.Filter(resources, func(res kube.GenericResource) bool {
|
||||
return res.ObjectMeta.GetNamespace() == namespace
|
||||
}).([]kube.GenericResource)
|
||||
objects := funk.Map(resources, func(res kube.GenericResource) interface{} {
|
||||
return res.Resource.Object
|
||||
}).([]interface{})
|
||||
|
||||
@@ -23,7 +23,8 @@ func (cs CountSummary) GetScore() uint {
|
||||
if total == 0 {
|
||||
return uint(100)
|
||||
}
|
||||
return uint((float64(cs.Successes*2) / float64(total)) * 100)
|
||||
score := uint((float64(cs.Successes*2) / float64(total)) * 100)
|
||||
return score
|
||||
}
|
||||
|
||||
// AddSummary adds two CountSummaries together
|
||||
|
||||
+27
-49
@@ -16,8 +16,6 @@ package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -56,37 +54,11 @@ func NewWebhook(mgr manager.Manager, validator Validator) {
|
||||
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
|
||||
}
|
||||
|
||||
// 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{}
|
||||
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, error) {
|
||||
var controller kube.GenericResource
|
||||
var err error
|
||||
if req.AdmissionRequest.Kind.Kind == "Pod" {
|
||||
pod := corev1.Pod{}
|
||||
err := v.decoder.Decode(req, &pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -95,58 +67,64 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult,
|
||||
logrus.Infof("Allowing owned pod %s/%s to pass through webhook", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
|
||||
return nil, nil
|
||||
}
|
||||
originalObject = pod
|
||||
controller, err = kube.NewGenericResourceFromPod(pod, pod)
|
||||
} else {
|
||||
pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw)
|
||||
controller, err = kube.NewGenericResourceFromBytes(req.Object.Raw)
|
||||
}
|
||||
controller, err := kube.NewGenericResourceFromPod(pod, originalObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
controller.Kind = req.AdmissionRequest.Kind.Kind
|
||||
var controllerResult validator.Result
|
||||
// TODO: consider enabling multi-resource checks
|
||||
controllerResult, err = validator.ApplyAllSchemaChecks(&v.Config, nil, controller)
|
||||
controllerResult, err := validator.ApplyAllSchemaChecks(&v.Config, nil, controller)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return controllerResult.PodResult, nil
|
||||
return &controllerResult, nil
|
||||
}
|
||||
|
||||
// Handle for Validator to run validation checks.
|
||||
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
logrus.Info("Starting request")
|
||||
podResult, err := v.handleInternal(req)
|
||||
result, err := v.handleInternal(req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error validating request: %v", err)
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
allowed := true
|
||||
reason := ""
|
||||
if podResult != nil {
|
||||
numDangers := podResult.GetSummary().Dangers
|
||||
if result != nil {
|
||||
numDangers := result.GetSummary().Dangers
|
||||
if numDangers > 0 {
|
||||
allowed = false
|
||||
reason = getFailureReason(*podResult)
|
||||
reason = getFailureReason(*result)
|
||||
}
|
||||
logrus.Infof("%d validation errors found when validating %s", numDangers, podResult.Name)
|
||||
logrus.Infof("%d validation errors found when validating %s", numDangers, result.Name)
|
||||
}
|
||||
return admission.ValidationResponse(allowed, reason)
|
||||
}
|
||||
|
||||
func getFailureReason(podResult validator.PodResult) string {
|
||||
func getFailureReason(result validator.Result) string {
|
||||
reason := "\nPolaris prevented this deployment due to configuration problems:\n"
|
||||
|
||||
for _, message := range podResult.Results {
|
||||
for _, message := range result.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
|
||||
reason += fmt.Sprintf("- %s: %s\n", result.Kind, message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerResult := range podResult.ContainerResults {
|
||||
for _, message := range containerResult.Results {
|
||||
podResult := result.PodResult
|
||||
if podResult != nil {
|
||||
for _, message := range podResult.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["all"]
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- nEt_aDmIn
|
||||
@@ -6,9 +6,8 @@ metadata:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- All
|
||||
@@ -0,0 +1,29 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
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
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: zookeeper
|
||||
namespace: rightnamespace
|
||||
labels:
|
||||
app: zookeeper
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: zookeeper
|
||||
image: zookeeper
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: zk-pdb
|
||||
namespace: wrongnamespace
|
||||
spec:
|
||||
minAvailable: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: zookeeper
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
minAvailable: 100%
|
||||
maxUnavailable: 5
|
||||
+2
-2
@@ -4,5 +4,5 @@ metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
status:
|
||||
disruptionsAllowed: 0
|
||||
spec:
|
||||
maxUnavailable: 0
|
||||
+2
-2
@@ -4,5 +4,5 @@ metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
status:
|
||||
disruptionsAllowed: 1
|
||||
spec:
|
||||
minAvailable: 100%
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
minAvaiable: 5
|
||||
maxUnavailable: 10%
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
maxUnavailable: 5%
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec:
|
||||
minAvaiable: 5
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
env: test
|
||||
spec: {}
|
||||
Reference in New Issue
Block a user