mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad197f34c2 | ||
|
|
e802c28801 | ||
|
|
68ca5a8d13 | ||
|
|
14d503580b | ||
|
|
0fc109c199 | ||
|
|
63fd576d3e | ||
|
|
f0c8ee256e | ||
|
|
d5cb68084e | ||
|
|
f7d2309608 | ||
|
|
d800e8d629 | ||
|
|
eebc9537a2 | ||
|
|
1fed099b53 |
+1
-1
@@ -1 +1 @@
|
||||
* @rbren @makoscafee @jordandoig @baderbuddy @shaswa
|
||||
* @rbren @makoscafee @baderbuddy @shaswa
|
||||
@@ -3,7 +3,7 @@
|
||||
<br>
|
||||
<h3>Best Practices for Kubernetes Workload Configuration</h3>
|
||||
<a href="https://github.com/FairwindsOps/polaris">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.1.6&color=239922">
|
||||
<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">
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
successMessage: disruptionsAllowed is greater than zero
|
||||
failureMessage: disruptionsAllowed is not greater than zero
|
||||
category: Reliability
|
||||
target: PodDisruptionBudget
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
type: object
|
||||
required:
|
||||
- disruptionsAllowed
|
||||
properties:
|
||||
disruptionsAllowed:
|
||||
type: integer
|
||||
minimum: 1
|
||||
@@ -1,7 +1,7 @@
|
||||
successMessage: Ingress has TLS configured
|
||||
failureMessage: Ingress does not have TLS configured
|
||||
category: Security
|
||||
target: Ingress
|
||||
target: networking.k8s.io/Ingress
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
|
||||
@@ -38,6 +38,7 @@ var auditOutputURL string
|
||||
var auditOutputFile string
|
||||
var auditOutputFormat string
|
||||
var resourceToAudit string
|
||||
var useColor bool
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(auditCmd)
|
||||
@@ -47,7 +48,8 @@ func init() {
|
||||
auditCmd.PersistentFlags().IntVar(&minScore, "set-exit-code-below-score", 0, "Set an exit code of 4 when the score is below this threshold (1-100).")
|
||||
auditCmd.PersistentFlags().StringVar(&auditOutputURL, "output-url", "", "Destination URL to send audit results.")
|
||||
auditCmd.PersistentFlags().StringVar(&auditOutputFile, "output-file", "", "Destination file for audit results.")
|
||||
auditCmd.PersistentFlags().StringVarP(&auditOutputFormat, "format", "f", "json", "Output format for results - json, yaml, or score.")
|
||||
auditCmd.PersistentFlags().StringVarP(&auditOutputFormat, "format", "f", "json", "Output format for results - json, yaml, pretty, or score.")
|
||||
auditCmd.PersistentFlags().BoolVar(&useColor, "color", true, "Whether to use color in pretty format.")
|
||||
auditCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
|
||||
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
|
||||
}
|
||||
@@ -61,7 +63,7 @@ var auditCmd = &cobra.Command{
|
||||
config.DisplayName = displayName
|
||||
}
|
||||
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat)
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat, useColor)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -75,9 +77,9 @@ var auditCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string) validator.AuditData {
|
||||
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)
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, workload, c)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
@@ -98,6 +100,8 @@ func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, wor
|
||||
if err == nil {
|
||||
outputBytes, err = yaml.JSONToYAML(jsonBytes)
|
||||
}
|
||||
} else if outputFormat == "pretty" {
|
||||
outputBytes = []byte(auditData.GetPrettyOutput(useColor))
|
||||
} else {
|
||||
outputBytes, err = json.MarshalIndent(auditData, "", " ")
|
||||
}
|
||||
|
||||
@@ -33,6 +33,13 @@ module.exports = {
|
||||
"/infrastructure-as-code",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Usage",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/cli",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Customization",
|
||||
collapsable: false,
|
||||
|
||||
@@ -42,7 +42,7 @@ const baseConfig = {
|
||||
],
|
||||
themeConfig: {
|
||||
docsRepo: "",
|
||||
docsDir: 'docs-md',
|
||||
docsDir: 'docs',
|
||||
editLinks: true,
|
||||
editLinkText: "Help us improve this page",
|
||||
logo: '/img/fairwinds-logo.svg',
|
||||
|
||||
@@ -26,4 +26,4 @@ fbq('track', 'PageView');
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-K5KK5H3');
|
||||
})(window,document,'script','dataLayer','GTM-TM95WXQ');
|
||||
|
||||
+5
-2
@@ -3,7 +3,7 @@
|
||||
<br>
|
||||
<h3>Best Practices for Kubernetes Workload Configuration</h3>
|
||||
<a href="https://github.com/FairwindsOps/polaris">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.0.0&color=239922">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=3.1.6&color=239922">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
|
||||
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
|
||||
@@ -11,11 +11,14 @@
|
||||
<a href="https://circleci.com/gh/FairwindsOps/polaris.svg">
|
||||
<img src="https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg">
|
||||
</a>
|
||||
<a href="https://insights.fairwinds.com/gh/FairwindsOps/polaris">
|
||||
<img src="https://insights.fairwinds.com/v0/gh/FairwindsOps/polaris/badge.svg">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Fairwinds' Polaris keeps your clusters sailing smoothly. It runs a variety of checks to ensure that
|
||||
Kubernetes pods and controllers are configured using best practices, helping you avoid
|
||||
problems in the future. Polaris can be run in a few different modes:
|
||||
problems in the future.
|
||||
|
||||
Polaris can be run in three different modes:
|
||||
* As a [dashboard](/dashboard), so you can audit what's running inside your cluster.
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
sidebarDepth: 0
|
||||
---
|
||||
|
||||
## 3.2.0
|
||||
* Add `--format=pretty` option for CLI output
|
||||
|
||||
## 3.1.6
|
||||
* Fix nil pointer issue with --only-output-failed-tests
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#### CLI Options
|
||||
|
||||
```
|
||||
# top-level commands
|
||||
audit
|
||||
Runs a one-time audit.
|
||||
dashboard
|
||||
Runs the webserver for Polaris dashboard.
|
||||
help
|
||||
Prints help, if you give it a command then it will print help for that command. Same as -h
|
||||
version
|
||||
Prints the version of Polaris
|
||||
webhook
|
||||
Runs the webhook webserver
|
||||
|
||||
# global flags
|
||||
-c, --config string Location of Polaris configuration file.
|
||||
--disallow-exemptions Disallow any exemptions from configuration file.
|
||||
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string Logrus log level. (default "info")
|
||||
|
||||
# dashboard flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
--base-path string Path on which the dashboard is served. (default "/")
|
||||
--display-name string An optional identifier for the audit.
|
||||
-h, --help help for dashboard
|
||||
--listening-address string Listening Address for the dashboard webserver.
|
||||
--load-audit-file string Runs the dashboard with data saved from a past audit.
|
||||
-p, --port int Port for the dashboard webserver. (default 8080)
|
||||
|
||||
# audit flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
--color Whether to use color in pretty format. (default true)
|
||||
--display-name string An optional identifier for the audit.
|
||||
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
|
||||
-h, --help help for audit
|
||||
--only-show-failed-tests If specified, audit output will only show failed tests.
|
||||
--output-file string Destination file for audit results.
|
||||
--output-url string Destination URL to send audit results.
|
||||
--resource string Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.
|
||||
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
|
||||
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
|
||||
|
||||
|
||||
# webhook flags
|
||||
--disable-webhook-config-installer disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.
|
||||
-h, --help help for webhook
|
||||
-p, --port int Port for the dashboard webserver. (default 9876)
|
||||
```
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#### CLI Options
|
||||
|
||||
```
|
||||
# top-level commands
|
||||
audit
|
||||
Runs a one-time audit.
|
||||
dashboard
|
||||
Runs the webserver for Polaris dashboard.
|
||||
help
|
||||
Prints help, if you give it a command then it will print help for that command. Same as -h
|
||||
version
|
||||
Prints the version of Polaris
|
||||
webhook
|
||||
Runs the webhook webserver
|
||||
|
||||
# high-level flags
|
||||
-c, --config string
|
||||
Location of Polaris configuration file
|
||||
--disallow-exemptions
|
||||
Disallow any exemptions from configuration file.
|
||||
-h, --help
|
||||
Help for Polaris (same as help command)
|
||||
--kubeconfig string
|
||||
Path to a kubeconfig. Only required if out-of-cluster.
|
||||
--log-level string
|
||||
Logrus log level (default "info")
|
||||
--master string
|
||||
The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.
|
||||
|
||||
# dashboard flags
|
||||
--audit-path string
|
||||
If specified, audits one or more YAML files instead of a cluster
|
||||
--base-path string
|
||||
Path on which the dashboard is served (default "/")
|
||||
--display-name string
|
||||
An optional identifier for the audit
|
||||
--load-audit-file string
|
||||
Runs the dashboard with data saved from a past audit.
|
||||
-p, --port int
|
||||
Port for the dashboard webserver (default 8080)
|
||||
|
||||
# audit flags
|
||||
--audit-path string
|
||||
If specified, audits one or more YAML files instead of a cluster
|
||||
--resource string
|
||||
If specified, audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend
|
||||
--display-name string
|
||||
An optional identifier for the audit
|
||||
--format string
|
||||
Output format for results - json, yaml, or score (default "json")
|
||||
--output-file string
|
||||
Destination file for audit results
|
||||
--output-url string
|
||||
Destination URL to send audit results
|
||||
--set-exit-code-below-score int
|
||||
Set an exit code of 4 when the score is below this threshold (1-100)
|
||||
--set-exit-code-on-danger
|
||||
Set an exit code of 3 when the audit contains danger-level issues.
|
||||
|
||||
# webhook flags
|
||||
--disable-webhook-config-installer
|
||||
disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
|
||||
-p, --port int
|
||||
Port for the webhook webserver (default 9876)
|
||||
```
|
||||
|
||||
@@ -22,7 +22,7 @@ polaris version
|
||||
|
||||
## Running in a CI pipeline
|
||||
|
||||
### Set minimum score for an exit code
|
||||
### Set minimum score for an exit code
|
||||
You can tell the CLI to set an exit code if it detects certain issues with your YAML files.
|
||||
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
|
||||
```bash
|
||||
@@ -31,6 +31,22 @@ polaris audit --audit-path ./deploy/ \
|
||||
--set-exit-code-below-score 90
|
||||
```
|
||||
|
||||
### Pretty-print results
|
||||
By default, results are output as JSON. You can get human-readable output with
|
||||
the `--format=pretty` flag:
|
||||
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--format=pretty
|
||||
```
|
||||
|
||||
You can also disable colors and emoji:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ \
|
||||
--format=pretty \
|
||||
--color=false
|
||||
```
|
||||
|
||||
### Output only showing failed tests
|
||||
The CLI to gives you ability to display results containing only failed tests.
|
||||
For example:
|
||||
|
||||
@@ -6,6 +6,7 @@ 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.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
|
||||
@@ -17,10 +18,11 @@ require (
|
||||
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.0
|
||||
github.com/sirupsen/logrus v1.8.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.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
|
||||
|
||||
@@ -166,7 +166,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
@@ -427,9 +429,13 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY
|
||||
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
|
||||
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -571,6 +577,7 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
|
||||
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
@@ -610,6 +617,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y=
|
||||
github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@@ -807,6 +816,7 @@ golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "3.1.6"
|
||||
Version = "3.2.0"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -2,10 +2,12 @@ package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// IsActionable determines whether a check is actionable given the current configuration
|
||||
func (conf Configuration) IsActionable(ruleID, namespace, controllerName, containerName string) bool {
|
||||
func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, containerName string) bool {
|
||||
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
|
||||
return false
|
||||
}
|
||||
@@ -13,7 +15,7 @@ func (conf Configuration) IsActionable(ruleID, namespace, controllerName, contai
|
||||
return true
|
||||
}
|
||||
for _, exemption := range conf.Exemptions {
|
||||
if exemption.Namespace != "" && exemption.Namespace != namespace {
|
||||
if exemption.Namespace != "" && exemption.Namespace != objMeta.GetNamespace() {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -27,7 +29,7 @@ func (conf Configuration) IsActionable(ruleID, namespace, controllerName, contai
|
||||
}
|
||||
|
||||
if len(exemption.Rules) == 0 || checkIfRuleMatches {
|
||||
if !isExemptionCheckMatched(exemption.ControllerNames, controllerName) {
|
||||
if !isExemptionCheckMatched(exemption.ControllerNames, objMeta.GetName()) {
|
||||
continue
|
||||
}
|
||||
if isExemptionCheckMatched(exemption.ContainerNames, containerName) {
|
||||
|
||||
@@ -18,6 +18,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
var confContainerTest = `
|
||||
@@ -68,26 +71,37 @@ exemptions:
|
||||
- namespace: polaris
|
||||
`
|
||||
|
||||
func createMeta(namespace, name string) metav1.Object {
|
||||
unst := unstructured.Unstructured{}
|
||||
obj, err := meta.Accessor(&unst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
obj.SetName(name)
|
||||
obj.SetNamespace(namespace)
|
||||
return obj
|
||||
}
|
||||
|
||||
func TestNamespaceExemptionForSpecifiedRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "prometheus", "controller1", "")
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("prometheus", "controller1"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -95,19 +109,19 @@ func TestNamespaceExemptionForAllRules(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "container11")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "container11")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "polaris", "controller1", "")
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("polaris", "controller1"), "")
|
||||
assert.False(t, actionable)
|
||||
}
|
||||
|
||||
@@ -115,28 +129,28 @@ func TestControllerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "container21")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "container21")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "container21")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller3", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller3"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "container31")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "container31")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -144,22 +158,22 @@ func TestOnlyContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container41")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container42")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container42")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller4", "container41")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller4"), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container41")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "container41")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "container41")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -167,25 +181,25 @@ func TestNamespaceAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container51")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container51")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "", "container51")
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "container51")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "", "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller5", "container51")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller5"), "container51")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -193,25 +207,25 @@ func TestControllerAndContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller6", "container61")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller6"), "container61")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", "", "controller6", "container61")
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("", "controller6"), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container61")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container61")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
@@ -219,33 +233,33 @@ func TestContainerExemption(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(confContainerTest))
|
||||
assert.NoError(t, err)
|
||||
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container71")
|
||||
actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container71")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "controller7", "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", "controller7"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container71")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container71")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container61")
|
||||
actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container61")
|
||||
assert.True(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "controller7", "container71")
|
||||
actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", "controller7"), "container71")
|
||||
assert.False(t, actionable)
|
||||
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", "kube-system", "controller8", "container71")
|
||||
actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("kube-system", "controller8"), "container71")
|
||||
assert.True(t, actionable)
|
||||
}
|
||||
|
||||
+19
-8
@@ -3,8 +3,10 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qri-io/jsonschema"
|
||||
"github.com/thoas/go-funk"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
@@ -13,16 +15,21 @@ import (
|
||||
type TargetKind string
|
||||
|
||||
const (
|
||||
// TargetController points to the controller's spec
|
||||
TargetController TargetKind = "Controller"
|
||||
// TargetContainer points to the container spec
|
||||
TargetContainer TargetKind = "Container"
|
||||
// TargetPod points to the pod spec
|
||||
TargetPod TargetKind = "Pod"
|
||||
// TargetController points to the controller's spec
|
||||
TargetController TargetKind = "Controller"
|
||||
// TargetIngress points to the ingress spec
|
||||
TargetIngress TargetKind = "Ingress"
|
||||
)
|
||||
|
||||
// HandledTargets is a list of target names that are explicitly handled
|
||||
var HandledTargets = []TargetKind{
|
||||
TargetController,
|
||||
TargetContainer,
|
||||
TargetPod,
|
||||
}
|
||||
|
||||
// SchemaCheck is a Polaris check that runs using JSON Schema
|
||||
type SchemaCheck struct {
|
||||
ID string `yaml:"id"`
|
||||
@@ -154,13 +161,17 @@ func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
|
||||
}
|
||||
|
||||
// IsActionable decides if this check applies to a particular target
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool {
|
||||
if check.Target != target {
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
|
||||
if funk.Contains(HandledTargets, target) {
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
} else if string(check.Target) != kind && !strings.HasSuffix(string(check.Target), "/"+kind) {
|
||||
return false
|
||||
}
|
||||
isIncluded := len(check.Controllers.Include) == 0
|
||||
for _, inclusion := range check.Controllers.Include {
|
||||
if inclusion == controllerType {
|
||||
if inclusion == kind {
|
||||
isIncluded = true
|
||||
break
|
||||
}
|
||||
@@ -169,7 +180,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, controllerType string,
|
||||
return false
|
||||
}
|
||||
for _, exclusion := range check.Controllers.Exclude {
|
||||
if exclusion == controllerType {
|
||||
if exclusion == kind {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
adjustedConf := getConfigForQuery(c, r.URL.Query())
|
||||
if auditData == nil {
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
|
||||
@@ -206,7 +206,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
adjustedConf := getConfigForQuery(c, r.URL.Query())
|
||||
|
||||
if auditData == nil {
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "", c)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -13,22 +12,23 @@ import (
|
||||
kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
// GenericWorkload is a base implementation with some free methods for inherited structs
|
||||
type GenericWorkload struct {
|
||||
// GenericResource is a base implementation with some free methods for inherited structs
|
||||
type GenericResource struct {
|
||||
Kind string
|
||||
PodSpec kubeAPICoreV1.PodSpec
|
||||
ObjectMeta kubeAPIMetaV1.Object
|
||||
Resource unstructured.Unstructured
|
||||
PodSpec *kubeAPICoreV1.PodSpec
|
||||
OriginalObjectJSON []byte
|
||||
}
|
||||
|
||||
// NewGenericWorkloadFromUnstructured creates a workload from an unstructured.Unstructured
|
||||
func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload := GenericWorkload{
|
||||
Kind: kind,
|
||||
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
|
||||
func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (GenericResource, error) {
|
||||
workload := GenericResource{
|
||||
Kind: unst.GetKind(),
|
||||
Resource: *unst,
|
||||
}
|
||||
|
||||
objMeta, err := meta.Accessor(unst)
|
||||
@@ -49,25 +49,27 @@ func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstruct
|
||||
return workload, err
|
||||
}
|
||||
podSpecMap := GetPodSpec(m)
|
||||
b, err = json.Marshal(podSpecMap)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
if podSpecMap != nil {
|
||||
b, err = json.Marshal(podSpecMap)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
podSpec := kubeAPICoreV1.PodSpec{}
|
||||
err = json.Unmarshal(b, &podSpec)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.PodSpec = &podSpec
|
||||
}
|
||||
podSpec := kubeAPICoreV1.PodSpec{}
|
||||
err = json.Unmarshal(b, &podSpec)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.PodSpec = podSpec
|
||||
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
// NewGenericWorkloadFromPod builds a new workload for a given Pod without looking at parents
|
||||
func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericWorkload, error) {
|
||||
workload := GenericWorkload{
|
||||
// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents
|
||||
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) {
|
||||
workload := GenericResource{
|
||||
Kind: "Pod",
|
||||
PodSpec: podResource.Spec,
|
||||
PodSpec: &podResource.Spec,
|
||||
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
|
||||
}
|
||||
if originalObject != nil {
|
||||
@@ -77,13 +79,12 @@ func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject int
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
|
||||
var unst unstructured.Unstructured
|
||||
err = json.Unmarshal(bytes, &unst.Object)
|
||||
err = json.Unmarshal(bytes, &workload.Resource.Object)
|
||||
if err != nil {
|
||||
logrus.Error("Couldn't marshal JSON for pod ", err)
|
||||
return workload, err
|
||||
}
|
||||
objMeta, err := meta.Accessor(&unst)
|
||||
objMeta, err := meta.Accessor(&workload.Resource)
|
||||
if err != nil {
|
||||
logrus.Error("Couldn't create meta accessor for unstructred ", err)
|
||||
return workload, err
|
||||
@@ -93,26 +94,36 @@ func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject int
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
// NewGenericWorkload builds a new workload for a given Pod
|
||||
func NewGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload, err := newGenericWorkload(ctx, podResource, dynamicClient, restMapper, objectCache)
|
||||
// NewGenericResourceFromBytes parses a generic kubernetes resource
|
||||
func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
|
||||
unst := unstructured.Unstructured{}
|
||||
err := yaml.Unmarshal(contentBytes, &unst.Object)
|
||||
if err != nil {
|
||||
return GenericResource{}, err
|
||||
}
|
||||
return NewGenericResourceFromUnstructured(&unst)
|
||||
}
|
||||
|
||||
// ResolveControllerFromPod builds a new workload for a given Pod
|
||||
func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
|
||||
workload, err := resolveControllerFromPod(ctx, podResource, dynamicClient, restMapper, objectCache)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
if len(workload.OriginalObjectJSON) == 0 {
|
||||
return NewGenericWorkloadFromPod(podResource, podResource)
|
||||
return NewGenericResourceFromPod(podResource, podResource)
|
||||
}
|
||||
return workload, err
|
||||
}
|
||||
|
||||
func newGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload, err := NewGenericWorkloadFromPod(podResource, nil)
|
||||
func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
|
||||
podWorkload, err := NewGenericResourceFromPod(podResource, nil)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
return podWorkload, err
|
||||
}
|
||||
// If an owner exists then set the name to the workload.
|
||||
// This allows us to handle CRDs creating Workloads or DeploymentConfigs in OpenShift.
|
||||
owners := workload.ObjectMeta.GetOwnerReferences()
|
||||
topKind := "Pod"
|
||||
topMeta := podWorkload.ObjectMeta
|
||||
owners := podResource.ObjectMeta.GetOwnerReferences()
|
||||
lastKey := ""
|
||||
for len(owners) > 0 {
|
||||
if len(owners) > 1 {
|
||||
@@ -122,12 +133,12 @@ func newGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dyna
|
||||
if firstOwner.Kind == "Node" {
|
||||
break
|
||||
}
|
||||
workload.Kind = firstOwner.Kind
|
||||
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, workload.ObjectMeta.GetNamespace(), firstOwner.Name)
|
||||
topKind = firstOwner.Kind
|
||||
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, topMeta.GetNamespace(), firstOwner.Name)
|
||||
lastKey = key
|
||||
abstractObject, ok := objectCache[key]
|
||||
if !ok {
|
||||
err = cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
|
||||
err := cacheAllObjectsOfKind(ctx, firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error caching objects of Kind %s %v", firstOwner.Kind, err)
|
||||
break
|
||||
@@ -142,26 +153,22 @@ func newGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dyna
|
||||
objMeta, err := meta.Accessor(&abstractObject)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent metadata %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err)
|
||||
return workload, err
|
||||
return GenericResource{}, err
|
||||
}
|
||||
workload.ObjectMeta = objMeta
|
||||
topMeta = objMeta
|
||||
owners = abstractObject.GetOwnerReferences()
|
||||
}
|
||||
|
||||
if lastKey != "" {
|
||||
unst := objectCache[lastKey]
|
||||
bytes, err := json.Marshal(&unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
} else {
|
||||
bytes, err := json.Marshal(podResource)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
return NewGenericResourceFromUnstructured(&unst)
|
||||
}
|
||||
workload, err := NewGenericResourceFromPod(podResource, podResource)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.Kind = topKind
|
||||
workload.ObjectMeta = topMeta
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
@@ -207,37 +214,3 @@ func GetPodSpec(yaml map[string]interface{}) interface{} {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWorkloadFromBytes parses a GenericWorkload
|
||||
func GetWorkloadFromBytes(contentBytes []byte) (*GenericWorkload, error) {
|
||||
yamlNode := make(map[string]interface{})
|
||||
err := yaml.Unmarshal(contentBytes, &yamlNode)
|
||||
if err != nil {
|
||||
logrus.Errorf("Invalid YAML: %s", string(contentBytes))
|
||||
return nil, err
|
||||
}
|
||||
finalDoc := make(map[string]interface{})
|
||||
finalDoc["metadata"] = yamlNode["metadata"]
|
||||
finalDoc["apiVersion"] = "v1"
|
||||
finalDoc["kind"] = "Pod"
|
||||
podSpec := GetPodSpec(yamlNode)
|
||||
if podSpec == nil {
|
||||
return nil, nil
|
||||
}
|
||||
finalDoc["spec"] = podSpec
|
||||
marshaledYaml, err := yaml.Marshal(finalDoc)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not marshal yaml: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000)
|
||||
pod := kubeAPICoreV1.Pod{}
|
||||
err = decoder.Decode(&pod)
|
||||
newController, err := NewGenericWorkloadFromPod(pod, yamlNode)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newController.Kind = yamlNode["kind"].(string)
|
||||
return &newController, nil
|
||||
}
|
||||
+127
-70
@@ -12,12 +12,15 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/thoas/go-funk"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -34,8 +37,59 @@ type ResourceProvider struct {
|
||||
SourceType string
|
||||
Nodes []corev1.Node
|
||||
Namespaces []corev1.Namespace
|
||||
Controllers []GenericWorkload
|
||||
Ingresses []v1beta1.Ingress
|
||||
Resources resourceKindMap
|
||||
}
|
||||
|
||||
type resourceKindMap map[string][]GenericResource
|
||||
|
||||
func (rkm resourceKindMap) addResource(r GenericResource) {
|
||||
rkm[r.Kind] = append(rkm[r.Kind], r)
|
||||
}
|
||||
|
||||
func (rkm resourceKindMap) addResources(rs []GenericResource) {
|
||||
for _, r := range rs {
|
||||
rkm.addResource(r)
|
||||
}
|
||||
}
|
||||
|
||||
func (rkm resourceKindMap) GetLength() int {
|
||||
total := 0
|
||||
for _, rs := range rkm {
|
||||
total += len(rs)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (rkm resourceKindMap) GetNumberOfControllers() int {
|
||||
total := 0
|
||||
for _, rs := range rkm {
|
||||
for _, r := range rs {
|
||||
if r.PodSpec != nil {
|
||||
total++
|
||||
}
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// This is here for backward compatibility reasons
|
||||
func maybeTransformKindIntoGroupKind(k conf.TargetKind) string {
|
||||
if k == "Ingress" {
|
||||
return "networking.k8s.io/Ingress"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func newResourceProvider(version, sourceType, sourceName string) ResourceProvider {
|
||||
return ResourceProvider{
|
||||
ServerVersion: version,
|
||||
SourceType: sourceType,
|
||||
SourceName: sourceName,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: make([]corev1.Node, 0),
|
||||
Namespaces: make([]corev1.Namespace, 0),
|
||||
Resources: make(map[string][]GenericResource),
|
||||
}
|
||||
}
|
||||
|
||||
type k8sResource struct {
|
||||
@@ -45,18 +99,18 @@ type k8sResource struct {
|
||||
var podSpecFields = []string{"jobTemplate", "spec", "template"}
|
||||
|
||||
// CreateResourceProvider returns a new ResourceProvider object to interact with k8s resources
|
||||
func CreateResourceProvider(ctx context.Context, directory, workload string) (*ResourceProvider, error) {
|
||||
func CreateResourceProvider(ctx context.Context, directory, workload string, c conf.Configuration) (*ResourceProvider, error) {
|
||||
if workload != "" {
|
||||
return CreateResourceProviderFromWorkload(ctx, workload)
|
||||
return CreateResourceProviderFromResource(ctx, workload)
|
||||
}
|
||||
if directory != "" {
|
||||
return CreateResourceProviderFromPath(directory)
|
||||
}
|
||||
return CreateResourceProviderFromCluster(ctx)
|
||||
return CreateResourceProviderFromCluster(ctx, c)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromWorkload creates a new ResourceProvider that just contains one workload
|
||||
func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (*ResourceProvider, error) {
|
||||
// CreateResourceProviderFromResource creates a new ResourceProvider that just contains one workload
|
||||
func CreateResourceProviderFromResource(ctx context.Context, workload string) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
@@ -72,14 +126,7 @@ func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (*
|
||||
logrus.Errorf("Error fetching Cluster API version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
resources := ResourceProvider{
|
||||
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
|
||||
SourceType: "Workload",
|
||||
SourceName: workload,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: []corev1.Node{},
|
||||
Namespaces: []corev1.Namespace{},
|
||||
}
|
||||
resources := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Resource", workload)
|
||||
|
||||
parts := strings.Split(workload, "/")
|
||||
if len(parts) != 4 {
|
||||
@@ -106,31 +153,24 @@ func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (*
|
||||
logrus.Errorf("Could not find workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
workloadObj, err := NewGenericWorkloadFromUnstructured(kind, obj)
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(obj)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not parse workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources.Controllers = []GenericWorkload{workloadObj}
|
||||
resources.Resources.addResource(workloadObj)
|
||||
return &resources, nil
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromPath returns a new ResourceProvider using the YAML files in a directory
|
||||
func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error) {
|
||||
resources := ResourceProvider{
|
||||
ServerVersion: "unknown",
|
||||
SourceType: "Path",
|
||||
SourceName: directory,
|
||||
Nodes: []corev1.Node{},
|
||||
Namespaces: []corev1.Namespace{},
|
||||
Controllers: []GenericWorkload{},
|
||||
}
|
||||
resources := newResourceProvider("unknown", "Path", directory)
|
||||
|
||||
if directory == "-" {
|
||||
fi, err := os.Stdin.Stat()
|
||||
if err == nil && fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||
if err := addResourcesFromReader(os.Stdin, &resources); err != nil {
|
||||
if err := resources.addResourcesFromReader(os.Stdin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resources, nil
|
||||
@@ -146,7 +186,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
logrus.Errorf("Error reading file: %v", path)
|
||||
return err
|
||||
}
|
||||
return addResourcesFromYaml(string(contents), &resources)
|
||||
return resources.addResourcesFromYaml(string(contents))
|
||||
}
|
||||
|
||||
err := filepath.Walk(directory, visitFile)
|
||||
@@ -157,7 +197,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromCluster creates a new ResourceProvider using live data from a cluster
|
||||
func CreateResourceProviderFromCluster(ctx context.Context) (*ResourceProvider, error) {
|
||||
func CreateResourceProviderFromCluster(ctx context.Context, c conf.Configuration) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
@@ -173,17 +213,18 @@ func CreateResourceProviderFromCluster(ctx context.Context) (*ResourceProvider,
|
||||
logrus.Errorf("Error connecting to dynamic interface: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface)
|
||||
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface, c)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface
|
||||
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface) (*ResourceProvider, error) {
|
||||
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface, c conf.Configuration) (*ResourceProvider, error) {
|
||||
listOpts := metav1.ListOptions{}
|
||||
serverVersion, err := kube.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Cluster API version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
provider := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Cluster", clusterName)
|
||||
|
||||
nodes, err := kube.CoreV1().Nodes().List(ctx, listOpts)
|
||||
if err != nil {
|
||||
@@ -200,11 +241,6 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
logrus.Errorf("Error fetching Pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
ingressList, err := kube.ExtensionsV1beta1().Ingresses("").List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Ingresses: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
|
||||
if err != nil {
|
||||
@@ -213,6 +249,35 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
}
|
||||
restMapper := restmapper.NewDiscoveryRESTMapper(resources)
|
||||
|
||||
var additionalKinds []conf.TargetKind
|
||||
for _, check := range c.CustomChecks {
|
||||
if !funk.Contains(conf.HandledTargets, check.Target) {
|
||||
additionalKinds = append(additionalKinds, check.Target)
|
||||
}
|
||||
}
|
||||
|
||||
for _, kind := range additionalKinds {
|
||||
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)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects, err := (*dynamic).Resource(mapping.Resource).Namespace("").List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v", mapping.Resource.Version, mapping.Resource.Resource, err)
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range objects.Items {
|
||||
res, err := NewGenericResourceFromUnstructured(&obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider.Resources.addResource(res)
|
||||
}
|
||||
}
|
||||
|
||||
objectCache := map[string]unstructured.Unstructured{}
|
||||
|
||||
controllers, err := LoadControllers(ctx, pods.Items, dynamic, &restMapper, objectCache)
|
||||
@@ -220,23 +285,15 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
logrus.Errorf("Error loading controllers from pods: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api := ResourceProvider{
|
||||
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
|
||||
SourceType: "Cluster",
|
||||
SourceName: clusterName,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: nodes.Items,
|
||||
Namespaces: namespaces.Items,
|
||||
Controllers: controllers,
|
||||
Ingresses: ingressList.Items,
|
||||
}
|
||||
return &api, nil
|
||||
provider.Nodes = nodes.Items
|
||||
provider.Namespaces = namespaces.Items
|
||||
provider.Resources.addResources(controllers)
|
||||
return &provider, nil
|
||||
}
|
||||
|
||||
// LoadControllers loads a list of controllers from the kubeResources Pods
|
||||
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericWorkload, error) {
|
||||
interfaces := []GenericWorkload{}
|
||||
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) {
|
||||
interfaces := []GenericResource{}
|
||||
deduped := map[string]corev1.Pod{}
|
||||
for _, pod := range pods {
|
||||
owners := pod.ObjectMeta.OwnerReferences
|
||||
@@ -247,7 +304,7 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe
|
||||
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod
|
||||
}
|
||||
for _, pod := range deduped {
|
||||
workload, err := NewGenericWorkload(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
workload, err := ResolveControllerFromPod(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -258,8 +315,8 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe
|
||||
|
||||
// Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates.
|
||||
// In cases like CronJobs older children can hang around, so this takes the most recent.
|
||||
func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkload {
|
||||
controllerMap := make(map[string]GenericWorkload)
|
||||
func deduplicateControllers(inputControllers []GenericResource) []GenericResource {
|
||||
controllerMap := make(map[string]GenericResource)
|
||||
for _, controller := range inputControllers {
|
||||
key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName()
|
||||
oldController, ok := controllerMap[key]
|
||||
@@ -267,32 +324,34 @@ func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkloa
|
||||
controllerMap[key] = controller
|
||||
}
|
||||
}
|
||||
results := make([]GenericWorkload, 0)
|
||||
results := make([]GenericResource, len(controllerMap))
|
||||
idx := 0
|
||||
for _, controller := range controllerMap {
|
||||
results = append(results, controller)
|
||||
results[idx] = controller
|
||||
idx++
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func addResourcesFromReader(reader io.Reader, resources *ResourceProvider) error {
|
||||
func (resources *ResourceProvider) addResourcesFromReader(reader io.Reader) error {
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error reading from %v: %v", reader, err)
|
||||
return err
|
||||
}
|
||||
if err := addResourcesFromYaml(string(contents), resources); err != nil {
|
||||
if err := resources.addResourcesFromYaml(string(contents)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addResourcesFromYaml(contents string, resources *ResourceProvider) error {
|
||||
func (resources *ResourceProvider) addResourcesFromYaml(contents string) error {
|
||||
specs := regexp.MustCompile("[\r\n]-+[\r\n]").Split(string(contents), -1)
|
||||
for _, spec := range specs {
|
||||
if strings.TrimSpace(spec) == "" {
|
||||
continue
|
||||
}
|
||||
err := addResourceFromString(spec, resources)
|
||||
err := resources.addResourceFromString(spec)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing YAML: (%v)", err)
|
||||
return err
|
||||
@@ -301,7 +360,7 @@ func addResourcesFromYaml(contents string, resources *ResourceProvider) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func addResourceFromString(contents string, resources *ResourceProvider) error {
|
||||
func (resources *ResourceProvider) addResourceFromString(contents string) error {
|
||||
contentBytes := []byte(contents)
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
|
||||
resource := k8sResource{}
|
||||
@@ -316,27 +375,25 @@ func addResourceFromString(contents string, resources *ResourceProvider) error {
|
||||
ns := corev1.Namespace{}
|
||||
err = decoder.Decode(&ns)
|
||||
resources.Namespaces = append(resources.Namespaces, ns)
|
||||
} else if resource.Kind == "Pod" {
|
||||
}
|
||||
|
||||
if resource.Kind == "Pod" {
|
||||
pod := corev1.Pod{}
|
||||
err = decoder.Decode(&pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workload, err := NewGenericWorkloadFromPod(pod, pod)
|
||||
workload, err := NewGenericResourceFromPod(pod, pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.Controllers = append(resources.Controllers, workload)
|
||||
} else if resource.Kind == "Ingress" {
|
||||
ingress := v1beta1.Ingress{}
|
||||
err = decoder.Decode(&ingress)
|
||||
resources.Ingresses = append(resources.Ingresses, ingress)
|
||||
resources.Resources.addResource(workload)
|
||||
} else {
|
||||
newController, err := GetWorkloadFromBytes(contentBytes)
|
||||
if err != nil || newController == nil {
|
||||
newResource, err := NewGenericResourceFromBytes(contentBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.Controllers = append(resources.Controllers, *newController)
|
||||
resources.Resources.addResource(newResource)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
+29
-31
@@ -3,36 +3,40 @@ package kube
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestGetResourcesFromPath(t *testing.T) {
|
||||
resources, err := CreateResourceProviderFromPath("./test_files/test_1")
|
||||
provider, err := CreateResourceProviderFromPath("./test_files/test_1")
|
||||
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
|
||||
assert.Equal(t, "Path", resources.SourceType, "Should have type Path")
|
||||
assert.Equal(t, "./test_files/test_1", resources.SourceName, "Should have filename as name")
|
||||
assert.Equal(t, "unknown", resources.ServerVersion, "Server version should be unknown")
|
||||
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")
|
||||
assert.Equal(t, "Path", provider.SourceType, "Should have type Path")
|
||||
assert.Equal(t, "./test_files/test_1", provider.SourceName, "Should have filename as name")
|
||||
assert.Equal(t, "unknown", provider.ServerVersion, "Server version should be unknown")
|
||||
assert.IsType(t, time.Now(), provider.CreationTime, "Creation time should be set")
|
||||
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
assert.Equal(t, 0, len(provider.Nodes), "Should not have any nodes")
|
||||
|
||||
assert.Equal(t, 1, len(resources.Namespaces), "Should have a namespace")
|
||||
assert.Equal(t, "two", resources.Namespaces[0].ObjectMeta.Name)
|
||||
assert.Equal(t, 1, len(provider.Namespaces), "Should have a namespace")
|
||||
assert.Equal(t, "two", provider.Namespaces[0].ObjectMeta.Name)
|
||||
|
||||
assert.Equal(t, 9, len(resources.Controllers), "Should have eight controllers")
|
||||
namespaceCount := map[string]int{}
|
||||
for _, controller := range resources.Controllers {
|
||||
namespaceCount[controller.ObjectMeta.GetNamespace()]++
|
||||
for kind, resources := range provider.Resources {
|
||||
fmt.Println("found", kind, len(resources))
|
||||
for _, controller := range resources {
|
||||
namespaceCount[controller.ObjectMeta.GetNamespace()]++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 8, namespaceCount[""])
|
||||
assert.Equal(t, 11, provider.Resources.GetLength())
|
||||
assert.Equal(t, 10, namespaceCount[""])
|
||||
assert.Equal(t, 1, namespaceCount["two"])
|
||||
}
|
||||
|
||||
@@ -48,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.Controllers), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Controllers[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)
|
||||
@@ -65,21 +69,14 @@ func TestAddResourcesFromReader(t *testing.T) {
|
||||
contents, err := ioutil.ReadFile("./test_files/test_2/multi.yaml")
|
||||
assert.NoError(t, err)
|
||||
reader := bytes.NewBuffer(contents)
|
||||
resources := &ResourceProvider{
|
||||
ServerVersion: "unknown",
|
||||
SourceType: "Path",
|
||||
SourceName: "-",
|
||||
Nodes: []corev1.Node{},
|
||||
Namespaces: []corev1.Namespace{},
|
||||
Controllers: []GenericWorkload{},
|
||||
}
|
||||
err = addResourcesFromReader(reader, resources)
|
||||
resources := newResourceProvider("unknown", "Path", "-")
|
||||
err = resources.addResourcesFromReader(reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
|
||||
assert.Equal(t, 1, len(resources.Controllers), "Should have one controller")
|
||||
assert.Equal(t, "dashboard", resources.Controllers[0].PodSpec.Containers[0].Name)
|
||||
assert.Equal(t, 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)
|
||||
@@ -88,7 +85,7 @@ func TestAddResourcesFromReader(t *testing.T) {
|
||||
|
||||
func TestGetResourceFromAPI(t *testing.T) {
|
||||
k8s, dynamicInterface := test.SetupTestAPI(test.GetMockControllers("test")...)
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface)
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface, conf.Configuration{})
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
|
||||
assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path")
|
||||
@@ -96,8 +93,7 @@ func TestGetResourceFromAPI(t *testing.T) {
|
||||
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")
|
||||
|
||||
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
|
||||
assert.Equal(t, 0, len(resources.Ingresses), "Should not have any ingresses")
|
||||
assert.Equal(t, 5, len(resources.Controllers), "Should have 5 controllers")
|
||||
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
|
||||
|
||||
expectedNames := map[string]bool{
|
||||
"deploy": false,
|
||||
@@ -106,8 +102,10 @@ func TestGetResourceFromAPI(t *testing.T) {
|
||||
"statefulset": false,
|
||||
"daemonset": false,
|
||||
}
|
||||
for _, ctrl := range resources.Controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
for _, controllers := range resources.Resources {
|
||||
for _, ctrl := range controllers {
|
||||
expectedNames[ctrl.ObjectMeta.GetName()] = true
|
||||
}
|
||||
}
|
||||
for name, val := range expectedNames {
|
||||
assert.Equal(t, true, val, name)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
network "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestValidatePDB(t *testing.T) {
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"pdbDisruptionsAllowedGreaterThanZero": conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
pdb := unstructured.Unstructured{}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&pdb)
|
||||
res.Kind = "PodDisruptionBudget"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
results := actualResult.Results["pdbDisruptionsAllowedGreaterThanZero"]
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func TestValidateIngress(t *testing.T) {
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"tlsSettingsMissing": conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
tls := network.IngressTLS{
|
||||
Hosts: []string{"test"},
|
||||
SecretName: "secret",
|
||||
}
|
||||
|
||||
ingress := network.Ingress{}
|
||||
ingress.Spec.TLS = []network.IngressTLS{tls}
|
||||
b, err := json.Marshal(ingress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
unst := unstructured.Unstructured{}
|
||||
err = json.Unmarshal(b, &unst.Object)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&unst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Kind = "Ingress"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
results := actualResult.Results["tlsSettingsMissing"]
|
||||
|
||||
assert.True(t, results.Success)
|
||||
assert.Equal(t, conf.SeverityWarning, results.Severity)
|
||||
assert.Equal(t, "Security", results.Category)
|
||||
assert.EqualValues(t, "Ingress has TLS configured", results.Message)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ValidateContainer validates a single container from a given controller
|
||||
func ValidateContainer(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ContainerResult, error) {
|
||||
results, err := applyContainerSchemaChecks(conf, controller, container, isInit)
|
||||
if err != nil {
|
||||
return ContainerResult{}, err
|
||||
}
|
||||
|
||||
cRes := ContainerResult{
|
||||
Name: container.Name,
|
||||
Results: results,
|
||||
}
|
||||
|
||||
return cRes, nil
|
||||
}
|
||||
|
||||
// ValidateAllContainers validates both init and regular containers
|
||||
func ValidateAllContainers(conf *config.Configuration, controller kube.GenericWorkload) ([]ContainerResult, error) {
|
||||
results := []ContainerResult{}
|
||||
pod := controller.PodSpec
|
||||
for _, container := range pod.InitContainers {
|
||||
result, err := ValidateContainer(conf, controller, &container, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
for _, container := range pod.Containers {
|
||||
result, err := ValidateContainer(conf, controller, &container, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
@@ -50,8 +50,8 @@ exemptions:
|
||||
- foo
|
||||
`
|
||||
|
||||
func getEmptyWorkload(t *testing.T, name string) kube.GenericWorkload {
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{
|
||||
func getEmptyWorkload(t *testing.T, name string) kube.GenericResource {
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
@@ -64,7 +64,7 @@ func testValidate(t *testing.T, container *corev1.Container, resourceConf *strin
|
||||
testValidateWithWorkload(t, container, resourceConf, getEmptyWorkload(t, controllerName), expectedDangers, expectedWarnings, expectedSuccesses)
|
||||
}
|
||||
|
||||
func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericWorkload, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) {
|
||||
func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericResource, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) {
|
||||
parsedConf, err := conf.Parse([]byte(*resourceConf))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
@@ -921,7 +921,7 @@ func TestValidateSecurity(t *testing.T) {
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, workload, tt.container, false)
|
||||
if err != nil {
|
||||
@@ -1066,7 +1066,7 @@ func TestValidateRunAsRoot(t *testing.T) {
|
||||
}
|
||||
for idx, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&config, workload, tt.container, false)
|
||||
if err != nil {
|
||||
@@ -1168,7 +1168,7 @@ func TestValidateResourcesEmptyContainerCPURequestsExempt(t *testing.T) {
|
||||
|
||||
expectedSuccesses := []ResultMessage{}
|
||||
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Annotations: map[string]string{
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
)
|
||||
|
||||
// ValidateController validates a single controller, returns a Result.
|
||||
func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (Result, error) {
|
||||
podResult, err := ValidatePod(conf, controller)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
var controllerResult ResultSet
|
||||
controllerResult, err = applyControllerSchemaChecks(conf, controller)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
result := Result{
|
||||
Kind: controller.Kind,
|
||||
Name: controller.ObjectMeta.GetName(),
|
||||
Namespace: controller.ObjectMeta.GetNamespace(),
|
||||
Results: controllerResult,
|
||||
PodResult: &podResult,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ValidateControllers validates that each deployment conforms to the Polaris config,
|
||||
// builds a list of ResourceResults organized by namespace.
|
||||
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
|
||||
controllersToAudit := kubeResources.Controllers
|
||||
|
||||
results := []Result{}
|
||||
for _, controller := range controllersToAudit {
|
||||
result, err := ValidateController(config, controller)
|
||||
if err != nil {
|
||||
logrus.Warn("An error occurred validating controller:", err)
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func TestValidateController(t *testing.T) {
|
||||
"hostPIDSet": conf.SeverityDanger,
|
||||
},
|
||||
}
|
||||
deployment, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
|
||||
deployment, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
|
||||
assert.NoError(t, err)
|
||||
deployment.Kind = "Deployment"
|
||||
expectedSum := CountSummary{
|
||||
@@ -49,7 +49,7 @@ func TestValidateController(t *testing.T) {
|
||||
}
|
||||
|
||||
var actualResult Result
|
||||
actualResult, err = ValidateController(&c, deployment)
|
||||
actualResult, err = applyControllerSchemaChecks(&c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -72,33 +72,31 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
Severity: "danger",
|
||||
Category: "Reliability",
|
||||
}
|
||||
for _, controller := range res.Controllers {
|
||||
if controller.Kind == "Deployment" {
|
||||
actualResult, err := ValidateController(&c, controller)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if controller.ObjectMeta.GetName() == "test-deployment-2" {
|
||||
expectedResult.Success = true
|
||||
expectedResult.Message = "Multiple replicas are scheduled"
|
||||
} else if controller.ObjectMeta.GetName() == "test-deployment" {
|
||||
expectedResult.Success = false
|
||||
expectedResult.Message = "Only one replica is scheduled"
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"multipleReplicasForDeployment": expectedResult,
|
||||
}
|
||||
|
||||
assert.Equal(t, "Deployment", actualResult.Kind)
|
||||
assert.Equal(t, 1, len(actualResult.Results), "should be equal")
|
||||
assert.EqualValues(t, expectedResults, actualResult.Results, controller.ObjectMeta.GetName())
|
||||
for _, controller := range res.Resources["Deployment"] {
|
||||
actualResult, err := applyControllerSchemaChecks(&c, controller)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if controller.ObjectMeta.GetName() == "test-deployment-2" {
|
||||
expectedResult.Success = true
|
||||
expectedResult.Message = "Multiple replicas are scheduled"
|
||||
} else if controller.ObjectMeta.GetName() == "test-deployment" {
|
||||
expectedResult.Success = false
|
||||
expectedResult.Message = "Only one replica is scheduled"
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"multipleReplicasForDeployment": expectedResult,
|
||||
}
|
||||
|
||||
assert.Equal(t, "Deployment", actualResult.Kind)
|
||||
assert.Equal(t, 1, len(actualResult.Results), "should be equal")
|
||||
assert.EqualValues(t, expectedResults, actualResult.Results, controller.ObjectMeta.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
res, err := kube.CreateResourceProviderFromPath("../kube/test_files/test_1")
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
assert.Equal(t, 9, len(res.Controllers), "Should have eight controllers")
|
||||
assert.Equal(t, 11, res.Resources.GetLength())
|
||||
testResources(res)
|
||||
|
||||
replicaSpec := map[string]interface{}{"replicas": 2}
|
||||
@@ -111,9 +109,9 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
two := int32(2)
|
||||
d2.Spec.Replicas = &two
|
||||
k8s, dynamicClient := test.SetupTestAPI(&d1, &p1, &d2, &p2)
|
||||
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient)
|
||||
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, conf.Configuration{})
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, 2, len(res.Controllers), "Should have two controllers")
|
||||
assert.Equal(t, 2, res.Resources.GetLength(), "Should have two controllers")
|
||||
testResources(res)
|
||||
}
|
||||
|
||||
@@ -126,7 +124,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
}
|
||||
pod := test.MockPod()
|
||||
pod.Spec.InitContainers = []corev1.Container{test.MockContainer("test")}
|
||||
deployment, err := kube.NewGenericWorkloadFromPod(pod, nil)
|
||||
deployment, err := kube.NewGenericResourceFromPod(pod, nil)
|
||||
assert.NoError(t, err)
|
||||
deployment.Kind = "Deployment"
|
||||
expectedSum := CountSummary{
|
||||
@@ -139,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 = ValidateController(&c, deployment)
|
||||
actualResult, err = applyControllerSchemaChecks(&c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -149,7 +147,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
assert.EqualValues(t, ResultSet{}, actualResult.PodResult.ContainerResults[0].Results)
|
||||
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[1].Results)
|
||||
|
||||
job, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
|
||||
job, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
|
||||
assert.NoError(t, err)
|
||||
job.Kind = "Job"
|
||||
expectedSum = CountSummary{
|
||||
@@ -158,7 +156,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = ValidateController(&c, job)
|
||||
actualResult, err = applyControllerSchemaChecks(&c, job)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -167,7 +165,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[0].Results)
|
||||
|
||||
cronjob, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
|
||||
cronjob, err := kube.NewGenericResourceFromPod(test.MockPod(), nil)
|
||||
assert.NoError(t, err)
|
||||
cronjob.Kind = "CronJob"
|
||||
expectedSum = CountSummary{
|
||||
@@ -176,7 +174,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = ValidateController(&c, cronjob)
|
||||
actualResult, err = applyControllerSchemaChecks(&c, cronjob)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -203,18 +201,16 @@ func TestControllerExemptions(t *testing.T) {
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
var actualResults []Result
|
||||
|
||||
pod := test.MockPod()
|
||||
pod.ObjectMeta.Namespace = "foo"
|
||||
workload, err := kube.NewGenericWorkloadFromPod(pod, nil)
|
||||
workload, err := kube.NewGenericResourceFromPod(pod, nil)
|
||||
assert.NoError(t, err)
|
||||
workload.Kind = "Deployment"
|
||||
resources := &kube.ResourceProvider{
|
||||
Controllers: []kube.GenericWorkload{workload},
|
||||
}
|
||||
resources := []kube.GenericResource{workload}
|
||||
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
var actualResults []Result
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -225,7 +221,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
c.Exemptions = []conf.Exemption{{
|
||||
Namespace: "foo",
|
||||
}}
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -234,10 +230,10 @@ func TestControllerExemptions(t *testing.T) {
|
||||
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
|
||||
|
||||
c.Exemptions = nil
|
||||
resources.Controllers[0].ObjectMeta.SetAnnotations(map[string]string{
|
||||
resources[0].ObjectMeta.SetAnnotations(map[string]string{
|
||||
exemptionAnnotationKey: "true",
|
||||
})
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -246,7 +242,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
|
||||
|
||||
c.DisallowExemptions = true
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -22,17 +22,14 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, o
|
||||
displayName = kubeResources.SourceName
|
||||
}
|
||||
|
||||
results, err := ValidateControllers(&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...)
|
||||
}
|
||||
controllerCount := len(results)
|
||||
|
||||
ingressResults, err := ValidateIngresses(&config, kubeResources)
|
||||
if err != nil {
|
||||
return AuditData{}, err
|
||||
}
|
||||
results = append(results, ingressResults...)
|
||||
|
||||
auditData := AuditData{
|
||||
PolarisOutputVersion: PolarisOutputVersion,
|
||||
@@ -43,9 +40,8 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, o
|
||||
ClusterInfo: ClusterInfo{
|
||||
Version: kubeResources.ServerVersion,
|
||||
Nodes: len(kubeResources.Nodes),
|
||||
Pods: len(kubeResources.Controllers), // TODO validate that this is still valuable
|
||||
Namespaces: len(kubeResources.Namespaces),
|
||||
Controllers: controllerCount,
|
||||
Controllers: kubeResources.Resources.GetNumberOfControllers(),
|
||||
},
|
||||
Results: results,
|
||||
}
|
||||
|
||||
@@ -11,11 +11,6 @@ import (
|
||||
)
|
||||
|
||||
func TestGetTemplateData(t *testing.T) {
|
||||
k8s, dynamicClient := test.SetupTestAPI(test.GetMockControllers("test")...)
|
||||
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, 5, len(resources.Controllers))
|
||||
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"readinessProbeMissing": conf.SeverityDanger,
|
||||
@@ -23,6 +18,11 @@ func TestGetTemplateData(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
k8s, dynamicClient := test.SetupTestAPI(test.GetMockControllers("test")...)
|
||||
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient, c)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, 5, len(resources.Resources))
|
||||
|
||||
sum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(3),
|
||||
@@ -57,8 +57,9 @@ func TestGetTemplateData(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
assert.Equal(t, 1, len(result.PodResult.ContainerResults))
|
||||
assert.Equal(t, expected.results, len(result.PodResult.ContainerResults[0].Results))
|
||||
if assert.Equal(t, 1, len(result.PodResult.ContainerResults), "bad container results for "+result.Kind) {
|
||||
assert.Equal(t, expected.results, len(result.PodResult.ContainerResults[0].Results))
|
||||
}
|
||||
}
|
||||
assert.Equal(t, found, true)
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
// ValidateIngresses validates all the ingresses in a ResourceProvider
|
||||
func ValidateIngresses(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
|
||||
var results []Result
|
||||
for _, ingress := range kubeResources.Ingresses {
|
||||
result, err := ValidateIngress(config, ingress)
|
||||
if err != nil {
|
||||
return []Result{}, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ValidateIngress validates a single ingress
|
||||
func ValidateIngress(config *conf.Configuration, ingress v1beta1.Ingress) (Result, error) {
|
||||
results, err := applyIngressSchemaChecks(config, ingress)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
result := Result{
|
||||
Kind: "Ingress",
|
||||
Name: ingress.ObjectMeta.GetName(),
|
||||
Namespace: ingress.ObjectMeta.GetNamespace(),
|
||||
Results: results,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
extv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
func TestValidateIngress(t *testing.T) {
|
||||
c := conf.Configuration{
|
||||
Checks: map[string]conf.Severity{
|
||||
"tlsSettingsMissing": conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
ingress := test.MockIngress()
|
||||
|
||||
var actualResult Result
|
||||
actualResult, err := ValidateIngress(&c, ingress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
results := actualResult.Results["tlsSettingsMissing"]
|
||||
|
||||
assert.False(t, results.Success)
|
||||
assert.Equal(t, conf.Severity("warning"), results.Severity)
|
||||
assert.Equal(t, "Security", results.Category)
|
||||
assert.EqualValues(t, "Ingress does not have TLS configured", results.Message)
|
||||
|
||||
tls := extv1beta1.IngressTLS{
|
||||
Hosts: []string{"test"},
|
||||
SecretName: "secret",
|
||||
}
|
||||
|
||||
ingress.Spec.TLS = []extv1beta1.IngressTLS{tls}
|
||||
actualResult, err = ValidateIngress(&c, ingress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
results = actualResult.Results["tlsSettingsMissing"]
|
||||
|
||||
assert.True(t, results.Success)
|
||||
assert.Equal(t, conf.Severity("warning"), results.Severity)
|
||||
assert.Equal(t, "Security", results.Category)
|
||||
assert.EqualValues(t, "Ingress has TLS configured", results.Message)
|
||||
|
||||
}
|
||||
@@ -15,8 +15,11 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
)
|
||||
|
||||
@@ -25,6 +28,17 @@ const (
|
||||
PolarisOutputVersion = "1.0"
|
||||
)
|
||||
|
||||
var (
|
||||
successMessage = "🎉 Success"
|
||||
dangerMessage = "❌ Danger"
|
||||
warningMessage = "😬 Warning"
|
||||
)
|
||||
|
||||
var (
|
||||
titleColor = color.New(color.FgBlue).Add(color.Bold)
|
||||
checkColor = color.New(color.FgCyan)
|
||||
)
|
||||
|
||||
// AuditData contains all the data from a full Polaris audit
|
||||
type AuditData struct {
|
||||
PolarisOutputVersion string
|
||||
@@ -114,3 +128,74 @@ type ContainerResult struct {
|
||||
func (res *ContainerResult) removeSuccessfulResults() {
|
||||
res.Results.removeSuccessfulResults()
|
||||
}
|
||||
|
||||
func fillString(id string, l int) string {
|
||||
for len(id) < l {
|
||||
id += " "
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// GetPrettyOutput returns a human-readable string
|
||||
func (res AuditData) GetPrettyOutput(useColor bool) string {
|
||||
color.NoColor = !useColor
|
||||
str := titleColor.Sprint(fmt.Sprintf("\n\nPolaris audited %s %s at %s\n", res.SourceType, res.SourceName, res.AuditTime))
|
||||
str += color.CyanString(fmt.Sprintf(" Nodes: %d | Namespaces: %d | Controllers: %d\n", res.ClusterInfo.Nodes, res.ClusterInfo.Namespaces, res.ClusterInfo.Controllers))
|
||||
str += color.GreenString(fmt.Sprintf(" Final score: %d\n", res.Score))
|
||||
str += "\n"
|
||||
for _, result := range res.Results {
|
||||
str += result.GetPrettyOutput() + "\n"
|
||||
}
|
||||
color.NoColor = false
|
||||
return str
|
||||
}
|
||||
|
||||
// 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 += res.Results.GetPrettyOutput()
|
||||
if res.PodResult != nil {
|
||||
str += res.PodResult.GetPrettyOutput()
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// GetPrettyOutput returns a human-readable string
|
||||
func (res PodResult) GetPrettyOutput() string {
|
||||
str := res.Results.GetPrettyOutput()
|
||||
for _, cont := range res.ContainerResults {
|
||||
str += cont.GetPrettyOutput() + "\n"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// GetPrettyOutput returns a human-readable string
|
||||
func (res ContainerResult) GetPrettyOutput() string {
|
||||
str := titleColor.Sprint(fmt.Sprintf(" Container %s\n", res.Name))
|
||||
str += res.Results.GetPrettyOutput()
|
||||
return str
|
||||
}
|
||||
|
||||
const minIDLength = 40
|
||||
|
||||
// GetPrettyOutput returns a human-readable string
|
||||
func (res ResultSet) GetPrettyOutput() string {
|
||||
indent := " "
|
||||
str := ""
|
||||
for _, msg := range res {
|
||||
status := color.GreenString(successMessage)
|
||||
if !msg.Success {
|
||||
if msg.Severity == config.SeverityWarning {
|
||||
status = color.YellowString(warningMessage)
|
||||
} else {
|
||||
status = color.RedString(dangerMessage)
|
||||
}
|
||||
}
|
||||
if color.NoColor {
|
||||
status = status[2:] // remove emoji
|
||||
}
|
||||
str += fmt.Sprintf("%s%s %s\n", indent, checkColor.Sprint(fillString(msg.ID, minIDLength-len(indent))), status)
|
||||
str += fmt.Sprintf("%s %s - %s\n", indent, msg.Category, msg.Message)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
)
|
||||
|
||||
// ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult.
|
||||
func ValidatePod(conf *config.Configuration, controller kube.GenericWorkload) (PodResult, error) {
|
||||
podResults, err := applyPodSchemaChecks(conf, controller)
|
||||
if err != nil {
|
||||
return PodResult{}, err
|
||||
}
|
||||
pRes := PodResult{
|
||||
Results: podResults,
|
||||
ContainerResults: []ContainerResult{},
|
||||
}
|
||||
|
||||
pRes.ContainerResults, err = ValidateAllContainers(conf, controller)
|
||||
if err != nil {
|
||||
return pRes, err
|
||||
}
|
||||
return pRes, nil
|
||||
}
|
||||
+20
-25
@@ -36,7 +36,7 @@ func TestValidatePod(t *testing.T) {
|
||||
}
|
||||
|
||||
p := test.MockPod()
|
||||
deployment, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
deployment, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(4),
|
||||
@@ -50,15 +50,14 @@ func TestValidatePod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, deployment)
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
}
|
||||
|
||||
func TestInvalidIPCPod(t *testing.T) {
|
||||
@@ -73,7 +72,7 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
|
||||
p := test.MockPod()
|
||||
p.Spec.HostIPC = true
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -86,15 +85,14 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
}
|
||||
|
||||
func TestInvalidNetworkPod(t *testing.T) {
|
||||
@@ -109,7 +107,7 @@ func TestInvalidNetworkPod(t *testing.T) {
|
||||
|
||||
p := test.MockPod()
|
||||
p.Spec.HostNetwork = true
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -123,15 +121,14 @@ func TestInvalidNetworkPod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
}
|
||||
|
||||
func TestInvalidPIDPod(t *testing.T) {
|
||||
@@ -146,7 +143,7 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
|
||||
p := test.MockPod()
|
||||
p.Spec.HostPID = true
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -160,15 +157,14 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"},
|
||||
}
|
||||
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
}
|
||||
|
||||
func TestExemption(t *testing.T) {
|
||||
@@ -192,7 +188,7 @@ func TestExemption(t *testing.T) {
|
||||
p.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
}
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
@@ -204,13 +200,12 @@ func TestExemption(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
var actualPodResult PodResult
|
||||
actualPodResult, err = ValidatePod(&c, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualPodResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.Results)
|
||||
assert.EqualValues(t, expectedResults, actualPodResult.PodResult.Results)
|
||||
}
|
||||
|
||||
+151
-82
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
@@ -46,11 +45,19 @@ var (
|
||||
"dangerousCapabilities",
|
||||
"insecureCapabilities",
|
||||
"priorityClassNotSet",
|
||||
// Ingress checks
|
||||
// Other checks
|
||||
"tlsSettingsMissing",
|
||||
"pdbDisruptionsAllowedGreaterThanZero",
|
||||
}
|
||||
)
|
||||
|
||||
type schemaTestCase struct {
|
||||
Target config.TargetKind
|
||||
Resource kube.GenericResource
|
||||
IsInitContianer bool
|
||||
Container *corev1.Container
|
||||
}
|
||||
|
||||
func init() {
|
||||
schemaBox = packr.New("Schemas", "../../checks")
|
||||
for _, checkID := range checkOrder {
|
||||
@@ -81,7 +88,10 @@ func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCheck(conf *config.Configuration, checkID, kind string, target config.TargetKind, meta metaV1.Object, containerName string, isInitContainer bool) (*config.SchemaCheck, error) {
|
||||
func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) {
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(test.Resource.ObjectMeta, checkID) {
|
||||
return nil, nil
|
||||
}
|
||||
check, ok := conf.CustomChecks[checkID]
|
||||
if !ok {
|
||||
check, ok = builtInChecks[checkID]
|
||||
@@ -90,12 +100,14 @@ func resolveCheck(conf *config.Configuration, checkID, kind string, target confi
|
||||
return nil, fmt.Errorf("Check %s not found", checkID)
|
||||
}
|
||||
|
||||
namespace := meta.GetNamespace()
|
||||
name := meta.GetName()
|
||||
if !conf.IsActionable(check.ID, namespace, name, containerName) {
|
||||
containerName := ""
|
||||
if test.Container != nil {
|
||||
containerName = test.Container.Name
|
||||
}
|
||||
if !conf.IsActionable(check.ID, test.Resource.ObjectMeta, containerName) {
|
||||
return nil, nil
|
||||
}
|
||||
if !check.IsActionable(target, kind, isInitContainer) {
|
||||
if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) {
|
||||
return nil, nil
|
||||
}
|
||||
return &check, nil
|
||||
@@ -119,8 +131,8 @@ func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bo
|
||||
const exemptionAnnotationKey = "polaris.fairwinds.com/exempt"
|
||||
const exemptionAnnotationPattern = "polaris.fairwinds.com/%s-exempt"
|
||||
|
||||
func hasExemptionAnnotation(ctrl kube.GenericWorkload, checkID string) bool {
|
||||
annot := ctrl.ObjectMeta.GetAnnotations()
|
||||
func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
|
||||
annot := objMeta.GetAnnotations()
|
||||
val := annot[exemptionAnnotationKey]
|
||||
if strings.ToLower(val) == "true" {
|
||||
return true
|
||||
@@ -133,101 +145,158 @@ func hasExemptionAnnotation(ctrl kube.GenericWorkload, checkID string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
|
||||
continue
|
||||
}
|
||||
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetPod, controller.ObjectMeta, "", false)
|
||||
|
||||
// ApplyAllSchemaChecksToAllResources applies available checks to a list of resources
|
||||
func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resources []kube.GenericResource) ([]Result, error) {
|
||||
results := []Result{}
|
||||
for _, resource := range resources {
|
||||
result, err := ApplyAllSchemaChecks(conf, resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
return results, err
|
||||
}
|
||||
passes, err := check.CheckPod(&controller.PodSpec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyControllerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
// ApplyAllSchemaChecks applies available checks to a single resource
|
||||
func ApplyAllSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) {
|
||||
if resource.PodSpec == nil {
|
||||
return applyNonControllerSchemaChecks(conf, resource)
|
||||
}
|
||||
return applyControllerSchemaChecks(conf, resource)
|
||||
}
|
||||
|
||||
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, resource, false)
|
||||
finalResult.Results = resultSet
|
||||
return finalResult, err
|
||||
}
|
||||
|
||||
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, resource, true)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
finalResult.Results = resultSet
|
||||
|
||||
podRS, err := applyPodSchemaChecks(conf, resource)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
podRes := PodResult{
|
||||
Results: podRS,
|
||||
ContainerResults: []ContainerResult{},
|
||||
}
|
||||
finalResult.PodResult = &podRes
|
||||
|
||||
for _, container := range resource.PodSpec.InitContainers {
|
||||
results, err := applyContainerSchemaChecks(conf, resource, &container, true)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
cRes := ContainerResult{
|
||||
Name: container.Name,
|
||||
Results: results,
|
||||
}
|
||||
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
|
||||
}
|
||||
for _, container := range resource.PodSpec.Containers {
|
||||
results, err := applyContainerSchemaChecks(conf, resource, &container, false)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
cRes := ContainerResult{
|
||||
Name: container.Name,
|
||||
Results: results,
|
||||
}
|
||||
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
|
||||
}
|
||||
|
||||
return finalResult, nil
|
||||
}
|
||||
|
||||
func applyTopLevelSchemaChecks(conf *config.Configuration, res kube.GenericResource, isController bool) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
Resource: res,
|
||||
}
|
||||
if isController {
|
||||
test.Target = config.TargetController
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericResource) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
Target: config.TargetPod,
|
||||
Resource: controller,
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
Target: config.TargetContainer,
|
||||
Resource: controller,
|
||||
Container: container,
|
||||
IsInitContianer: isInit,
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
}
|
||||
|
||||
func applySchemaChecks(conf *config.Configuration, test schemaTestCase) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
|
||||
continue
|
||||
}
|
||||
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetController, controller.ObjectMeta, "", false)
|
||||
|
||||
result, err := applySchemaCheck(conf, checkID, test)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
return results, err
|
||||
}
|
||||
passes, err := check.CheckController(controller.OriginalObjectJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if result != nil {
|
||||
results[checkID] = *result
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) {
|
||||
continue
|
||||
}
|
||||
check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetContainer, controller.ObjectMeta, container.Name, isInit)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
var passes bool
|
||||
if check.SchemaTarget == config.TargetPod {
|
||||
podCopy := controller.PodSpec
|
||||
func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*ResultMessage, error) {
|
||||
check, err := resolveCheck(conf, checkID, test)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var passes bool
|
||||
if check.SchemaTarget != "" {
|
||||
if check.SchemaTarget == config.TargetPod && check.Target == config.TargetContainer {
|
||||
podCopy := *test.Resource.PodSpec
|
||||
podCopy.InitContainers = []corev1.Container{}
|
||||
podCopy.Containers = []corev1.Container{*container}
|
||||
podCopy.Containers = []corev1.Container{*test.Container}
|
||||
passes, err = check.CheckPod(&podCopy)
|
||||
} else {
|
||||
passes, err = check.CheckContainer(container)
|
||||
return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
} else if check.Target == config.TargetPod {
|
||||
passes, err = check.CheckPod(test.Resource.PodSpec)
|
||||
} else if check.Target == config.TargetContainer {
|
||||
passes, err = check.CheckContainer(test.Container)
|
||||
} else {
|
||||
passes, err = check.CheckObject(test.Resource.Resource.Object)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyIngressSchemaChecks(conf *config.Configuration, ingress v1beta1.Ingress) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
check, err := resolveCheck(conf, checkID, ingress.Kind, config.TargetIngress, ingress.ObjectMeta.GetObjectMeta(), "", false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
passes, err := check.CheckObject(ingress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return results, nil
|
||||
result := makeResult(conf, check, passes)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func getSortedKeys(m map[string]config.Severity) []string {
|
||||
|
||||
@@ -99,13 +99,13 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult,
|
||||
} else {
|
||||
pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw)
|
||||
}
|
||||
controller, err := kube.NewGenericWorkloadFromPod(pod, originalObject)
|
||||
controller, err := kube.NewGenericResourceFromPod(pod, originalObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
controller.Kind = req.AdmissionRequest.Kind.Kind
|
||||
var controllerResult validator.Result
|
||||
controllerResult, err = validator.ValidateController(&v.Config, controller)
|
||||
controllerResult, err = validator.ApplyAllSchemaChecks(&v.Config, controller)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: minimal-ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /testpath
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: test
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: minimal-ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
spec:
|
||||
tls:
|
||||
- secretName: example
|
||||
hosts:
|
||||
- example.com
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /testpath
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: test
|
||||
port:
|
||||
number: 80
|
||||
+19
-12
@@ -1,6 +1,7 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -52,22 +53,28 @@ func init() {
|
||||
|
||||
func TestChecks(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
workload, err := kube.GetWorkloadFromBytes(tc.input)
|
||||
assert.NoError(t, err)
|
||||
res, err := kube.NewGenericResourceFromBytes(tc.input)
|
||||
if err != nil {
|
||||
fmt.Println("error parsing", string(tc.input))
|
||||
panic(err)
|
||||
}
|
||||
c, err := config.Parse([]byte("checks:\n " + tc.check + ": danger"))
|
||||
assert.NoError(t, err)
|
||||
var result validator.Result
|
||||
result, err = validator.ValidateController(&c, *workload)
|
||||
result, err := validator.ApplyAllSchemaChecks(&c, res)
|
||||
assert.NoError(t, err)
|
||||
summary := result.GetSummary()
|
||||
if tc.failure {
|
||||
message := "Check " + tc.check + " passed unexpectedly"
|
||||
assert.Equal(t, uint(0), summary.Successes, message)
|
||||
assert.Equal(t, uint(1), summary.Dangers, message)
|
||||
} else {
|
||||
message := "Check " + tc.check + " failed unexpectedly"
|
||||
assert.Equal(t, uint(1), summary.Successes, message)
|
||||
assert.Equal(t, uint(0), summary.Dangers, message)
|
||||
total := summary.Successes + summary.Dangers
|
||||
msg := fmt.Sprintf("Check %s ran %d times instead of 1", tc.check, total)
|
||||
if assert.Equal(t, uint(1), total, msg) {
|
||||
if tc.failure {
|
||||
message := "Check " + tc.check + " passed unexpectedly"
|
||||
assert.Equal(t, uint(0), summary.Successes, message)
|
||||
assert.Equal(t, uint(1), summary.Dangers, message)
|
||||
} else {
|
||||
message := "Check " + tc.check + " failed unexpectedly"
|
||||
assert.Equal(t, uint(1), summary.Successes, message)
|
||||
assert.Equal(t, uint(0), summary.Dangers, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-20
@@ -19,7 +19,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func newUnstructured(apiVersion, kind, namespace, name string, spec interface{}) unstructured.Unstructured {
|
||||
func newUnstructured(apiVersion, kind, namespace, name string, spec map[string]interface{}) unstructured.Unstructured {
|
||||
return unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": apiVersion,
|
||||
@@ -69,7 +69,7 @@ func MockIngress() extv1beta1.Ingress {
|
||||
}
|
||||
|
||||
// MockController creates a mock controller and pod
|
||||
func MockController(apiVersion, kind, namespace, name string, spec interface{}, podSpec corev1.PodSpec, dest interface{}) corev1.Pod {
|
||||
func MockController(apiVersion, kind, namespace, name string, spec map[string]interface{}, podSpec corev1.PodSpec, dest interface{}) corev1.Pod {
|
||||
unst := newUnstructured(apiVersion, kind, namespace, name, spec)
|
||||
pod := corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -143,25 +143,10 @@ func MockJob(namespace, name string) (batchv1.Job, corev1.Pod) {
|
||||
func MockCronJob(namespace, name string) (batchv1beta1.CronJob, corev1.Pod) {
|
||||
cj := batchv1beta1.CronJob{}
|
||||
p := MockPod()
|
||||
b, err := json.Marshal(p.Spec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pSpec := map[string]interface{}{}
|
||||
err = json.Unmarshal(b, &pSpec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
spec := map[string]interface{}{
|
||||
"job_template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": pSpec,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
spec := map[string]interface{}{}
|
||||
pod := MockController("batch/v1beta1", "CronJob", namespace, name, spec, p.Spec, &cj)
|
||||
cj.Spec.JobTemplate.Spec.Template.Spec = pod.Spec
|
||||
|
||||
return cj, pod
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user