Compare commits

...
300 Commits
Author SHA1 Message Date
dirien c33452bba0 Update footer.gohtml (#386)
* Update footer.gohtml

update the date to 2020 in the footer of the dashboard

* Update main.go

* Update CHANGELOG.md
2020-08-09 11:53:02 -04:00
dependabot-preview[bot]andlnx01 300c8d9d94 Bump github.com/census-instrumentation/opencensus-proto (#381)
Bumps [github.com/census-instrumentation/opencensus-proto](https://github.com/census-instrumentation/opencensus-proto) from 0.2.1 to 0.3.0.
- [Release notes](https://github.com/census-instrumentation/opencensus-proto/releases)
- [Commits](https://github.com/census-instrumentation/opencensus-proto/compare/v0.2.1...v0.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-30 14:46:39 -04:00
Robert Brennan 7b1f046586 update version (#380)
* update version

* update changelog
2020-07-22 14:01:49 -04:00
Robert Brennan 4e290e192d Update config.yaml (#378)
* Update config.yaml

* Update config-full.yaml

* update docs
2020-07-22 13:48:30 -04:00
Robert Brennan 8d562f2490 fix for parent tree climbing (#379) 2020-07-22 13:31:49 -04:00
Robert Brennan 47150155a0 add ability to audit a single workload (#368)
* add ability to audit a single workload

* delint

* fix help text

* update readme
2020-07-22 13:28:02 -04:00
dependabot-preview[bot]andlnx01 b3d323d742 Bump github.com/imdario/mergo from 0.3.9 to 0.3.10 (#376)
Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.9 to 0.3.10.
- [Release notes](https://github.com/imdario/mergo/releases)
- [Commits](https://github.com/imdario/mergo/compare/v0.3.9...v0.3.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-21 10:37:11 -04:00
dependabot-preview[bot]andlnx01 6060cf243f Bump cloud.google.com/go from 0.60.0 to 0.61.0 (#375)
Bumps [cloud.google.com/go](https://github.com/googleapis/google-cloud-go) from 0.60.0 to 0.61.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/v0.60.0...v0.61.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-21 10:36:56 -04:00
Justin BurrandJustin Burr e044a540ed Fix CLI flags documentation. (#374)
Co-authored-by: Justin Burr <juburr@users.noreply.github.com>
2020-07-17 10:46:37 -04:00
dependabot-preview[bot]andlnx01 f6a4bffe88 Bump google.golang.org/api from 0.28.0 to 0.29.0 (#373)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.28.0 to 0.29.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.28.0...v0.29.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-15 15:42:54 -04:00
Robert Brennan 343e24886d bump to 1.1.1 (#372) 2020-07-10 10:03:12 -04:00
Robert Brennan 997d4bfb2b Handle orphaned pods (#371)
* handle orphaned pods

* set originalResource when creating Pod resources
2020-07-10 09:43:33 -04:00
dependabot-preview[bot]andlnx01 9ded3269c6 Bump cloud.google.com/go from 0.59.0 to 0.60.0 (#369)
Bumps [cloud.google.com/go](https://github.com/googleapis/google-cloud-go) from 0.59.0 to 0.60.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/v0.59.0...v0.60.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-09 15:24:00 -04:00
dependabot-preview[bot]andlnx01 ca65a446bd Bump cloud.google.com/go from 0.58.0 to 0.59.0 (#366)
Bumps [cloud.google.com/go](https://github.com/googleapis/google-cloud-go) from 0.58.0 to 0.59.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/v0.58.0...v0.59.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 14:00:39 -04:00
Samuel Barabas 4e9aa21cea Show controller check results on dashboard (#364)
As I mentioned in https://github.com/FairwindsOps/polaris/issues/351,
custom check results for `target: Controller` have not been shown on the
Dashboard.
2020-06-29 13:48:02 -04:00
dependabot-preview[bot]andlnx01 ed014ecf8e Bump github.com/prometheus/client_golang from 1.7.0 to 1.7.1 (#365)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.7.0...v1.7.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 13:46:39 -04:00
dependabot-preview[bot]andlnx01 2e11c04664 Bump google.golang.org/grpc from 1.29.1 to 1.30.0 (#367)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.29.1 to 1.30.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.29.1...v1.30.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 13:46:06 -04:00
Robert Brennan 80662bd203 update to version 1.1.0 (#362) 2020-06-23 08:50:28 -04:00
Robert Brennan 5705f81911 throw error when severity isn't set for custom check (#360)
* throw error when severity isn't set for custom check

* fix syntax
2020-06-22 16:53:15 -04:00
Robert Brennan b4e3d40f4b Add priority class check, some test infra (#342)
* add check for priority-class

* add test message

* lint
2020-06-22 16:34:48 -04:00
dependabot-preview[bot]andlnx01 cea0ca7fa8 Bump golang.org/x/text from 0.3.2 to 0.3.3 (#355)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.2 to 0.3.3.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.2...v0.3.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-22 16:08:17 -04:00
Robert Brennan 69bed16e4f don't error out when resolving parent resource (#358) 2020-06-22 16:05:23 -04:00
Huang Huang 5b173cf4a7 Support read yaml contents of workload from stdin (#353) 2020-06-22 14:19:51 -04:00
hgoscenski-vail 0a0720a26c Adds option to exempt an entire controller from checks via config file (#350)
This adds the ability to exempt a controller from all checks similar to
the annotation for "exempt" which exempts all checks.

I added the tests to go with this as well as for the IsActionable
function.
2020-06-22 14:18:23 -04:00
Robert Brennan fa3504c350 add custom checks to config (#340) 2020-06-22 14:16:15 -04:00
dependabot-preview[bot]andlnx01 4020000d68 Bump go.opencensus.io from 0.22.3 to 0.22.4 (#354)
Bumps [go.opencensus.io](https://github.com/census-instrumentation/opencensus-go) from 0.22.3 to 0.22.4.
- [Release notes](https://github.com/census-instrumentation/opencensus-go/releases)
- [Commits](https://github.com/census-instrumentation/opencensus-go/compare/v0.22.3...v0.22.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-22 14:15:56 -04:00
dependabot-preview[bot]andlnx01 ab7eeccfb0 Bump github.com/prometheus/client_golang from 1.6.0 to 1.7.0 (#356)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.6.0...v1.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-22 14:15:33 -04:00
dependabot-preview[bot]andlnx01 78a16fa0ea Bump google.golang.org/api from 0.26.0 to 0.28.0 (#357)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.26.0 to 0.28.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.26.0...v0.28.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-22 14:15:23 -04:00
Huang Huang b26c4be8d6 Support audit files which use \r or \r\n as newline character (#343) 2020-06-16 10:46:28 -04:00
dependabot-preview[bot]andlnx01 aada33f0e9 Bump cloud.google.com/go from 0.56.0 to 0.58.0 (#347)
Bumps [cloud.google.com/go](https://github.com/googleapis/google-cloud-go) from 0.56.0 to 0.58.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/v0.56.0...v0.58.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-15 10:39:10 -04:00
dependabot-preview[bot]andlnx01 7b88c48a6e Bump github.com/prometheus/procfs from 0.1.0 to 0.1.3 (#345)
Bumps [github.com/prometheus/procfs](https://github.com/prometheus/procfs) from 0.1.0 to 0.1.3.
- [Release notes](https://github.com/prometheus/procfs/releases)
- [Commits](https://github.com/prometheus/procfs/compare/v0.1.0...v0.1.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-15 10:28:24 -04:00
dependabot-preview[bot]andlnx01 ded260ee19 Bump github.com/json-iterator/go from 1.1.9 to 1.1.10 (#346)
Bumps [github.com/json-iterator/go](https://github.com/json-iterator/go) from 1.1.9 to 1.1.10.
- [Release notes](https://github.com/json-iterator/go/releases)
- [Commits](https://github.com/json-iterator/go/compare/v1.1.9...v1.1.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-15 10:17:07 -04:00
Robert Brennan 3e9c270ac7 fix zero-state (#341)
* fix zero-state

* fix lint error

* fix denom checks
2020-06-10 16:53:40 -04:00
baderbuddy bf6e41566d Don't assume all objects have pods. (#329)
* Don't assume all objects have pods.

* Test service is ignored

* Fix test

* Fix comment on test
2020-06-10 10:22:59 -04:00
dependabot-preview[bot]andlnx01 ededabf00f Bump contrib.go.opencensus.io/exporter/ocagent from 0.4.12 to 0.7.0 (#335)
Bumps [contrib.go.opencensus.io/exporter/ocagent](https://github.com/census-ecosystem/opencensus-go-exporter-ocagent) from 0.4.12 to 0.7.0.
- [Release notes](https://github.com/census-ecosystem/opencensus-go-exporter-ocagent/releases)
- [Commits](https://github.com/census-ecosystem/opencensus-go-exporter-ocagent/compare/v0.4.12...v0.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 07:55:38 -04:00
dependabot-preview[bot]andlnx01 3df9d35c57 Bump google.golang.org/api from 0.25.0 to 0.26.0 (#334)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.25.0 to 0.26.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.25.0...v0.26.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 07:55:26 -04:00
dependabot-preview[bot]andlnx01 a90c3b0f12 Bump github.com/stretchr/testify from 1.5.1 to 1.6.1 (#333)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.5.1 to 1.6.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.5.1...v1.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 07:55:15 -04:00
dependabot-preview[bot]andlnx01 d19b0769b4 Bump gopkg.in/yaml.v2 from 2.2.8 to 2.3.0 (#332)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.8 to 2.3.0.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.8...v2.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 07:55:06 -04:00
dependabot-preview[bot]andlnx01 97f25cd7d1 Bump github.com/prometheus/procfs from 0.0.11 to 0.1.0 (#331)
Bumps [github.com/prometheus/procfs](https://github.com/prometheus/procfs) from 0.0.11 to 0.1.0.
- [Release notes](https://github.com/prometheus/procfs/releases)
- [Commits](https://github.com/prometheus/procfs/compare/v0.0.11...v0.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 07:54:55 -04:00
Jordan Doig 03331147ca Merge pull request #320 from FairwindsOps/jd/search-filter
Filter by Namespace on Dashboard
2020-06-04 12:39:28 -06:00
jordandoig d44138bead Update filter section styles 2020-06-03 20:26:25 -06:00
jordandoig eb21b030c2 Add filters javascript 2020-06-03 20:19:18 -06:00
jordandoig 3061756a55 Filter by namespaces in query, style expandable filter section, check checkboxes on page load 2020-06-03 20:19:18 -06:00
jordandoig 2964280976 Add namespace filters section 2020-06-03 20:19:18 -06:00
Robert Brennan f657071a7f fix goreleaser (#326)
* fix goreleaser

* update rok8s
2020-06-03 17:26:37 -04:00
Robert Brennan 2cdc4775e0 bump to 1.0.3 (#325) 2020-06-03 16:56:24 -04:00
Robert Brennan af5b86e28c catch cache error (#324) 2020-06-03 15:55:13 -04:00
Robert Brennan 1021f6b1d7 fix basepath link (#319) 2020-06-03 15:42:06 -04:00
Robert Brennan 0111b7ec2b update documentation (#318) 2020-06-03 14:43:15 -04:00
Robert Brennan 33524757aa set minor version in polaris tag (#323) 2020-06-03 14:42:36 -04:00
dependabot-preview[bot]andlnx01 eea4a8e2c3 Bump google.golang.org/api from 0.5.0 to 0.25.0 (#317)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.5.0 to 0.25.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/master/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.5.0...v0.25.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-01 08:45:51 -04:00
dependabot-preview[bot]andlnx01 fb98d3112d Bump github.com/gobuffalo/mapi from 1.2.0 to 1.2.1 (#316)
Bumps [github.com/gobuffalo/mapi](https://github.com/gobuffalo/mapi) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/gobuffalo/mapi/releases)
- [Commits](https://github.com/gobuffalo/mapi/compare/v1.2.0...v1.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-01 08:45:42 -04:00
dependabot-preview[bot]andlnx01 97c81ccf60 Bump github.com/golang/protobuf from 1.4.0 to 1.4.2 (#315)
Bumps [github.com/golang/protobuf](https://github.com/golang/protobuf) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/golang/protobuf/releases)
- [Commits](https://github.com/golang/protobuf/compare/v1.4.0...v1.4.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-01 08:45:33 -04:00
dependabot-preview[bot]andlnx01 e9b79fbdd4 Bump k8s.io/klog from 0.3.0 to 0.4.0 (#314)
Bumps [k8s.io/klog](https://github.com/kubernetes/klog) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/kubernetes/klog/releases)
- [Changelog](https://github.com/kubernetes/klog/blob/master/RELEASE.md)
- [Commits](https://github.com/kubernetes/klog/compare/v0.3.0...v0.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-01 08:45:23 -04:00
dependabot-preview[bot]andlnx01 217e3563d6 Bump github.com/imdario/mergo from 0.3.7 to 0.3.9 (#312)
Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.7 to 0.3.9.
- [Release notes](https://github.com/imdario/mergo/releases)
- [Commits](https://github.com/imdario/mergo/compare/v0.3.7...v0.3.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-28 08:41:39 -04:00
dependabot-preview[bot]andlnx01 deb33bb05f Bump github.com/gogo/protobuf from 1.2.1 to 1.3.1 (#310)
Bumps [github.com/gogo/protobuf](https://github.com/gogo/protobuf) from 1.2.1 to 1.3.1.
- [Release notes](https://github.com/gogo/protobuf/releases)
- [Commits](https://github.com/gogo/protobuf/compare/v1.2.1...v1.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-28 08:41:10 -04:00
dependabot-preview[bot]andlnx01 e605eef755 Bump github.com/grpc-ecosystem/grpc-gateway from 1.9.0 to 1.14.6 (#306)
Bumps [github.com/grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) from 1.9.0 to 1.14.6.
- [Release notes](https://github.com/grpc-ecosystem/grpc-gateway/releases)
- [Changelog](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/CHANGELOG.md)
- [Commits](https://github.com/grpc-ecosystem/grpc-gateway/compare/v1.9.0...v1.14.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-28 08:40:59 -04:00
Robert Brennan c304036f97 Revert "go mod tidy"
This reverts commit 3eb2b6e2c9.
2020-05-21 11:14:48 +00:00
Robert Brennan c1061b50e0 Revert "fix go.sum"
This reverts commit 3b07496a4d.
2020-05-21 11:14:36 +00:00
Robert Brennan 3b07496a4d fix go.sum 2020-05-21 11:08:09 +00:00
Robert Brennan 3eb2b6e2c9 go mod tidy 2020-05-21 10:42:00 +00:00
Robert Brennan 5f5da069fd update deps 2020-05-21 10:38:58 +00:00
Robert Brennan ceeffb97a0 update to 1.0.2 2020-05-21 10:29:59 +00:00
Robert Brennan 27faab919c handle case where RBAC is missing for a CRD (#305)
* handle case where RBAC is missing for a CRD

* handle error
2020-05-21 06:27:15 -04:00
Robert Brennan f4fc7c1b5d add ARM support (#303) 2020-05-20 10:40:08 -04:00
Robert Brennan 54d796b1f3 update go.sum 2020-05-20 14:04:49 +00:00
Robert Brennan 9065e3b2ae fix dependencies (#302)
* fix dependencies

* revert packr2 change
2020-05-20 09:59:04 -04:00
Robert Brennan bd14ab8bc1 Release 1.0.0 (#300)
* Release 1.0.0

* update deploy files

* remove docs for old capabilities

* update images

* update image

* update docs

* remove capabilities language

* add CLI changes to changelog

* reorg changelog
2020-05-20 09:00:41 -04:00
Robert Brennan 2ac6a2b540 Change error to danger (#299)
* rename 'error' to 'danger'

* update dashboard

* fix docs

* update deploy configs
2020-05-19 08:41:07 -04:00
Robert Brennan 9d81c393de Pack config.yaml in Docker (#298)
* update examples

* pack config.yaml in Docker
2020-05-18 17:16:16 -04:00
Robert Brennan b557786325 Stop webhook from preventing scaling events (#293)
* test deployment scaling

* stop watching pods in the webhook

* fix check

* add pod check back

* skip webhook for owned pods
2020-05-18 15:17:21 -04:00
Robert Brennan 5572bb8118 fix up goreleaser (#297)
* fix up goreleaser

* revert version number change
2020-05-18 15:17:08 -04:00
Robert Brennan ac9412920f sort results on dashboard (#295)
This reverts commit 0c671d0f62.
2020-05-18 15:01:12 -04:00
baderbuddy d50d9c81f8 Add the capability for controller level checks (#285)
* Add controller level checks

* Add check for multipleReplicas

* Fixed spec

* Add controller level check

* Move controller schema checks to their own function.
2020-05-18 14:57:35 -04:00
Robert Brennan de98d9fbeb Update logo (#296)
* update logo

* fix image
2020-05-18 13:32:06 -04:00
Robert Brennan eac15d6258 more exemptions (#294) 2020-05-18 13:31:37 -04:00
Robert Brennan 0c671d0f62 Revert "sort results on dashboard"
This reverts commit e9064dfddc.
2020-05-18 17:21:22 +00:00
Robert Brennan e9064dfddc sort results on dashboard 2020-05-18 17:18:23 +00:00
Robert Brennan cf10a9617f add some exemptions for kube-system (#292) 2020-05-18 12:46:32 -04:00
dependabot-preview[bot]andlnx01 c571b97bc9 Bump github.com/gobuffalo/packd from 0.3.0 to 0.4.0 (#290)
Bumps [github.com/gobuffalo/packd](https://github.com/gobuffalo/packd) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/gobuffalo/packd/releases)
- [Commits](https://github.com/gobuffalo/packd/compare/v0.3.0...v0.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-18 12:40:50 -04:00
dependabot-preview[bot]andlnx01 fa5954c9ce Bump github.com/evanphx/json-patch (#287)
Bumps [github.com/evanphx/json-patch](https://github.com/evanphx/json-patch) from 4.2.0+incompatible to 4.5.0+incompatible.
- [Release notes](https://github.com/evanphx/json-patch/releases)
- [Commits](https://github.com/evanphx/json-patch/compare/v4.2.0...v4.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-18 12:40:35 -04:00
dependabot-preview[bot]andlnx01 58ec098c05 Bump github.com/konsorten/go-windows-terminal-sequences (#289)
Bumps [github.com/konsorten/go-windows-terminal-sequences](https://github.com/konsorten/go-windows-terminal-sequences) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/konsorten/go-windows-terminal-sequences/releases)
- [Commits](https://github.com/konsorten/go-windows-terminal-sequences/compare/v1.0.2...v1.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-18 12:40:22 -04:00
dependabot-preview[bot]andlnx01 c9211b9e56 Bump github.com/prometheus/common from 0.9.1 to 0.10.0 (#288)
Bumps [github.com/prometheus/common](https://github.com/prometheus/common) from 0.9.1 to 0.10.0.
- [Release notes](https://github.com/prometheus/common/releases)
- [Commits](https://github.com/prometheus/common/compare/v0.9.1...v0.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-18 12:40:10 -04:00
Robert Brennan a3e9939f76 don't push docker images on forked PRs (#291)
* don't push docker images on forked PRs

* fix name
2020-05-18 11:12:28 -04:00
baderbuddy 450d065f55 Update dependencies (#286) 2020-05-12 09:45:52 -04:00
Barnabas Makonda 9ce3c06d5c Merge pull request #283 from FairwindsOps/rb/slack-invite
Fix Slack invite
2020-05-12 09:19:00 +02:00
Robert Brennan ee951c8460 Fix Slack invite 2020-05-08 15:31:03 -04:00
69621f7034 Improve performance (#278)
* rename root fs check

* speed up docker build

* refactor webhook to be more generic

* delete controllers pkg

* revert deploy

* fix example config

* remove controllersToScan config

* fix lint error

* fix webhook name

* FileSystem -> Filesystem

* update deps

* skip node owners

* clean up meta tracking

* Cache results of dynamic queries

* Dynamically pick types to list.

* Fix unit tests

* Fix the other tests I missed

* Fix container test

* Fix issues from PR feedback

Co-authored-by: Robert Brennan <bobby.brennan@gmail.com>
Co-authored-by: Robert Brennan <accounts@rbren.io>
2020-05-01 13:29:29 -04:00
4cf3d15af0 Bump github.com/sirupsen/logrus from 1.4.2 to 1.5.0 (#277)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.4.2 to 1.5.0.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.4.2...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Robert Brennan <accounts@rbren.io>
2020-04-27 10:43:28 -04:00
5d875a225c Bump google.golang.org/appengine from 1.6.0 to 1.6.6 (#276)
Bumps [google.golang.org/appengine](https://github.com/golang/appengine) from 1.6.0 to 1.6.6.
- [Release notes](https://github.com/golang/appengine/releases)
- [Commits](https://github.com/golang/appengine/compare/v1.6.0...v1.6.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Robert Brennan <accounts@rbren.io>
2020-04-27 10:43:16 -04:00
Robert BrennanandRobert Brennan 6792fba91f Delete controllers package (#270)
* rename root fs check

* speed up docker build

* refactor webhook to be more generic

* delete controllers pkg

* revert deploy

* fix example config

* remove controllersToScan config

* fix lint error

* fix webhook name

* FileSystem -> Filesystem

* update deps

* skip node owners

* clean up meta tracking

Co-authored-by: Robert Brennan <bobby.brennan@gmail.com>
2020-04-27 10:43:02 -04:00
dependabot-preview[bot]andlnx01 f559f3328b Bump github.com/google/gofuzz from 1.0.0 to 1.1.0 (#275)
Bumps [github.com/google/gofuzz](https://github.com/google/gofuzz) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/google/gofuzz/releases)
- [Commits](https://github.com/google/gofuzz/compare/v1.0.0...v1.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-27 08:56:47 -04:00
dependabot-preview[bot]andlnx01 38e512291d Bump github.com/gorilla/mux from 1.7.2 to 1.7.4 (#274)
Bumps [github.com/gorilla/mux](https://github.com/gorilla/mux) from 1.7.2 to 1.7.4.
- [Release notes](https://github.com/gorilla/mux/releases)
- [Commits](https://github.com/gorilla/mux/compare/v1.7.2...v1.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-27 08:56:32 -04:00
Robert Brennan cfc0d213cd Fix slack link (#271) 2020-04-20 09:00:41 -04:00
dependabot-preview[bot]andlnx01 22125549a6 Bump github.com/prometheus/procfs from 0.0.2 to 0.0.11 (#269)
Bumps [github.com/prometheus/procfs](https://github.com/prometheus/procfs) from 0.0.2 to 0.0.11.
- [Release notes](https://github.com/prometheus/procfs/releases)
- [Commits](https://github.com/prometheus/procfs/compare/v0.0.2...v0.0.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-20 08:31:59 -04:00
dependabot-preview[bot]andlnx01 9e8378aece Bump go.uber.org/zap from 1.14.0 to 1.14.1 (#268)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.14.0 to 1.14.1.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.14.0...v1.14.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-20 08:31:47 -04:00
dependabot-preview[bot]andlnx01 79603c8cf7 Bump github.com/rogpeppe/go-internal from 1.5.1 to 1.5.2 (#267)
Bumps [github.com/rogpeppe/go-internal](https://github.com/rogpeppe/go-internal) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/rogpeppe/go-internal/releases)
- [Commits](https://github.com/rogpeppe/go-internal/compare/v1.5.1...v1.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-20 08:31:35 -04:00
dependabot-preview[bot]andlnx01 c21163aadc Bump github.com/pkg/errors from 0.8.1 to 0.9.1 (#266)
Bumps [github.com/pkg/errors](https://github.com/pkg/errors) from 0.8.1 to 0.9.1.
- [Release notes](https://github.com/pkg/errors/releases)
- [Commits](https://github.com/pkg/errors/compare/v0.8.1...v0.9.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-20 08:31:12 -04:00
baderbuddy c03ecbde4a Merge pull request #240 from FairwindsOps/bb/openshift
Adding support for third-party Controllers (e.g. OpenShift)
2020-03-26 13:06:36 -04:00
baderbuddy 57f3a8b911 Merge branch 'master' into bb/openshift 2020-03-26 10:50:47 -04:00
dependabot-preview[bot]andlnx01 37660e0fe9 Bump github.com/prometheus/common from 0.4.0 to 0.9.1 (#261)
Bumps [github.com/prometheus/common](https://github.com/prometheus/common) from 0.4.0 to 0.9.1.
- [Release notes](https://github.com/prometheus/common/releases)
- [Commits](https://github.com/prometheus/common/compare/v0.4.0...v0.9.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-26 10:50:38 -04:00
baderbuddy dcf02b9549 Merge branch 'master' into bb/openshift 2020-03-25 16:53:44 -04:00
Bader Boland 3c46f405a9 Cleanup SupportedControllers 2020-03-25 16:50:12 -04:00
dependabot-preview[bot]andlnx01 94548d0dcf Bump github.com/census-instrumentation/opencensus-proto (#260)
Bumps [github.com/census-instrumentation/opencensus-proto](https://github.com/census-instrumentation/opencensus-proto) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/census-instrumentation/opencensus-proto/releases)
- [Commits](https://github.com/census-instrumentation/opencensus-proto/compare/v0.2.0...v0.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-25 15:56:44 -04:00
dependabot-preview[bot]andlnx01 1497c9a872 Bump github.com/golang/protobuf from 1.3.1 to 1.3.5 (#259)
Bumps [github.com/golang/protobuf](https://github.com/golang/protobuf) from 1.3.1 to 1.3.5.
- [Release notes](https://github.com/golang/protobuf/releases)
- [Commits](https://github.com/golang/protobuf/compare/v1.3.1...v1.3.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-25 15:56:33 -04:00
Bader Boland a5828a2d3b Fix tests 2020-03-25 14:23:18 -04:00
Bader Boland 5769acf820 deduplicate results right away 2020-03-25 13:25:26 -04:00
baderbuddy aa0658b5ac Merge branch 'master' into bb/openshift 2020-03-23 09:27:54 -04:00
Bader Boland 49dbd1d33b Merge branch 'bb/openshift' of github.com:FairwindsOps/polaris into bb/openshift 2020-03-23 09:27:43 -04:00
Bader Boland 68fe23018a Feedback from PR 2020-03-23 09:27:36 -04:00
baderbuddy ea9db7e273 Merge pull request #263 from FairwindsOps/dependabot/go_modules/github.com/hashicorp/golang-lru-0.5.4
Bump github.com/hashicorp/golang-lru from 0.5.1 to 0.5.4
2020-03-23 08:24:01 -04:00
dependabot-preview[bot] 56ba00f524 Bump github.com/hashicorp/golang-lru from 0.5.1 to 0.5.4
Bumps [github.com/hashicorp/golang-lru](https://github.com/hashicorp/golang-lru) from 0.5.1 to 0.5.4.
- [Release notes](https://github.com/hashicorp/golang-lru/releases)
- [Commits](https://github.com/hashicorp/golang-lru/compare/v0.5.1...v0.5.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-23 11:34:36 +00:00
Robert Brennan d18bf855cb Merge branch 'master' into bb/openshift 2020-03-19 13:27:19 -04:00
dependabot-preview[bot]andlnx01 cbe1f104da Bump github.com/Azure/go-autorest (#255)
Bumps [github.com/Azure/go-autorest](https://github.com/Azure/go-autorest) from 12.0.0+incompatible to 12.4.3+incompatible.
- [Release notes](https://github.com/Azure/go-autorest/releases)
- [Changelog](https://github.com/Azure/go-autorest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Azure/go-autorest/compare/v12.0.0...v12.4.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-19 13:27:05 -04:00
dependabot-preview[bot]andlnx01 6eb12b3bfe Bump go.uber.org/multierr from 1.1.0 to 1.5.0 (#254)
Bumps [go.uber.org/multierr](https://github.com/uber-go/multierr) from 1.1.0 to 1.5.0.
- [Release notes](https://github.com/uber-go/multierr/releases)
- [Changelog](https://github.com/uber-go/multierr/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/multierr/compare/v1.1.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-19 13:26:52 -04:00
baderbuddy 98533bc11a Merge branch 'master' into bb/openshift 2020-03-18 16:45:15 -04:00
baderbuddy e4b8992301 Add myself to codeowners (#257) 2020-03-18 09:17:15 -04:00
baderbuddy ee2ccb0c26 Merge branch 'master' into bb/openshift 2020-03-17 17:14:43 -04:00
Bader Boland 61ecb69ab1 Adding support for v2alpha1 for cron 2020-03-17 17:00:30 -04:00
Bader Boland c43ace22a3 Add support for CronJobs in support versions list 2020-03-17 16:59:15 -04:00
Bader Boland fb0d7c5d82 Add additional logging 2020-03-17 16:19:58 -04:00
dependabot-preview[bot]andlnx01 403d424d66 Bump github.com/stretchr/testify from 1.4.0 to 1.5.1 (#253)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.4.0 to 1.5.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.4.0...v1.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-17 14:25:59 -04:00
dependabot-preview[bot]andlnx01 760e57c520 Bump github.com/gobuffalo/genny from 0.4.1 to 0.6.0 (#252)
Bumps [github.com/gobuffalo/genny](https://github.com/gobuffalo/genny) from 0.4.1 to 0.6.0.
- [Release notes](https://github.com/gobuffalo/genny/releases)
- [Commits](https://github.com/gobuffalo/genny/compare/v0.4.1...v0.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-17 14:25:48 -04:00
dependabot-preview[bot]andlnx01 72b8bfa8f6 Bump go.uber.org/zap from 1.10.0 to 1.14.0 (#251)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.10.0 to 1.14.0.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.10.0...v1.14.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-17 14:25:34 -04:00
Bader Boland 8c769e445c Cut out duplicitive code. 2020-03-17 13:23:58 -04:00
Bader Boland 3c685279be Cut out logic specific to controller types 2020-03-17 12:41:44 -04:00
Bader Boland 7fdebfc4db Fix tests 2020-03-17 09:19:33 -04:00
Bader Boland 0d3fe6130a Remove unnecessary queries 2020-03-16 16:48:57 -04:00
Bader Boland bb34be7e02 Dynamically retrieve parents 2020-03-16 16:41:16 -04:00
baderbuddy fe55216e2a Merge branch 'master' into bb/openshift 2020-03-16 13:07:20 -04:00
Alexander Lyon 3191f087bb Fix chart repository name (#256)
charts.fairwindsops.com doesn't resolve, fixed according to FairwindsOps/charts
2020-03-14 12:51:09 -04:00
Robert Brennan e80311d937 Merge pull request #249 from FairwindsOps/rb/slack
Add Slack link to readme
2020-03-10 16:16:37 -04:00
Robert Brennan d4717302d4 Merge branch 'master' into rb/slack 2020-03-09 13:43:52 -04:00
Robert Brennan a5d54af2b8 Merge pull request #248 from FairwindsOps/rb/yaml
fix deploy yaml
2020-03-09 13:43:33 -04:00
Robert Brennan 1b2a930a9c Update README.md 2020-03-09 12:25:11 -04:00
Robert Brennan 33d4192871 fix test deploy files 2020-03-06 18:39:56 +00:00
Robert Brennan 2a0ed94d0d fix deploy yaml 2020-03-06 16:47:08 +00:00
Robert Brennan 9ff5d5a70e Merge pull request #247 from BobVanB/patch-1
Command argument error `polaris --webhook`
2020-03-06 10:13:15 -05:00
Bob van Bokkem 6a1cee846a Command argument error polaris --webhook 2020-03-06 08:12:52 +01:00
Robert Brennan cd002d0aff Merge pull request #245 from FairwindsOps/rb/ctrl-versions
Add support for more controller versions
2020-02-26 14:59:46 -05:00
Robert Brennan d2bb2f126b test extra controller versions 2020-02-26 19:26:18 +00:00
Robert Brennan ec4e755f2f remove debug logs 2020-02-25 21:40:23 +00:00
Robert Brennan 3e9193af7f remove cronjob cases 2020-02-25 21:39:46 +00:00
Robert Brennan efd0ec85b9 fix lint error 2020-02-25 21:27:53 +00:00
Robert Brennan c036c91247 add cronjob webhook test cases 2020-02-25 21:23:40 +00:00
Robert Brennan dad526245d don't exit if webhook registration fails 2020-02-25 21:23:31 +00:00
Robert Brennan c591765f70 add more webhook test cases, remove cronjob case 2020-02-25 21:05:32 +00:00
Robert Brennan 51f3eaa3f0 add more webhook test cases, suport for cronjobs v2alpha1 2020-02-25 20:21:22 +00:00
Robert Brennan 574a1483eb add to supported controllers list 2020-02-25 17:49:20 +00:00
Robert Brennan a0604ba3a5 add failing v2beta2 test case 2020-02-25 16:29:02 +00:00
Robert Brennan 0da4ea69bf support some old controller versions 2020-02-25 16:25:34 +00:00
Robert Brennan 6213291be8 Merge pull request #242 from FairwindsOps/ejether/rework-webhook-language
Change webhook language to admission controller
2020-02-21 14:37:58 -05:00
baderbuddy 619e17d7a1 Merge branch 'master' into bb/openshift 2020-02-19 15:34:14 -05:00
EJ Etherington c64ba4bf91 updating language aroudn the webook because admission controller is what people are really searching for, not webook 2020-02-17 17:09:33 -08:00
baderbuddy 44572936ec Merge pull request #241 from FairwindsOps/bb/cobra-cmd
Refactor commands to use Cobra
2020-02-13 14:22:01 -05:00
Bader Boland 9c9e58c23d Removed commit from version info 2020-02-11 15:06:57 -05:00
Bader Boland b9d6a1ea85 Updated yaml 2020-02-11 08:28:52 -05:00
Bader Boland 4c345f3799 Missed a few c -> config. 2020-02-10 08:33:58 -05:00
Bader Boland 6009ee67fa Cleanup code 2020-02-10 08:27:08 -05:00
Bader Boland ddbf2e8c11 t push
Merge branch 'bb/cobra-cmd' of github.com:FairwindsOps/polaris into bb/cobra-cmd
2020-02-06 10:55:03 -05:00
Bader Boland edaea49cd0 Found more spots to update documentation. 2020-02-06 10:54:55 -05:00
Bader Boland 54727d933d Fixed typo for homebrew instructions. 2020-02-06 10:54:55 -05:00
Bader Boland 3e1cc3c9e9 Updating CI for new command names. 2020-02-06 10:54:55 -05:00
Bader Boland 1cae69d080 Updated documentation 2020-02-06 10:54:55 -05:00
Bader Boland 6f8028fc17 Moved the rest of the commands. 2020-02-06 10:54:55 -05:00
Bader Boland d507039d5f First commands added. 2020-02-06 10:54:54 -05:00
Robert Brennan b5ac0bc5d6 Merge branch 'master' into bb/openshift 2020-02-03 08:23:20 -05:00
Bader Boland 31de106ba8 Added check for parent and rollup to parent. 2020-01-31 14:10:53 -05:00
Bader Boland aaa54cc401 Directly return 0 for divide by 0 2020-01-31 12:57:26 -05:00
Bader Boland 51d639e2ef Prevent divide by 0 2020-01-31 12:56:53 -05:00
Bader Boland 330d7f0516 Add note about config change 2020-01-31 12:44:55 -05:00
baderbuddy 73c4923334 Merge pull request #239 from FairwindsOps/bb/exclude-check
Exclude individual checks from a controller
2020-01-29 12:53:04 -05:00
Bader Boland 10fdf4ef33 Updating CLA 2020-01-28 09:44:12 -05:00
Bader Boland ed48daa092 Updated docs 2020-01-28 09:35:20 -05:00
Bader Boland 7f71a352a7 Remove kebab case 2020-01-28 09:34:52 -05:00
Bader Boland ba412ab407 Undoing version bump 2020-01-28 09:32:02 -05:00
Bader Boland 7b0817202c Bumping version in main.go 2020-01-28 08:31:05 -05:00
Bader Boland 36876072f2 Bumping version and adding documentation. 2020-01-28 08:30:50 -05:00
Bader Boland b7b5c43cdf Found more spots to update documentation. 2020-01-24 13:15:41 -05:00
Bader Boland 100b4e8b9e Fixed typo for homebrew instructions. 2020-01-24 13:11:40 -05:00
Bader Boland e873a40cc3 Updating CI for new command names. 2020-01-24 13:08:30 -05:00
Bader Boland 1d9968b3af Updated documentation 2020-01-24 13:06:26 -05:00
Bader Boland 6338c53c2e Moved the rest of the commands. 2020-01-24 11:29:51 -05:00
Bader Boland 0c70bacf4f First commands added. 2020-01-24 11:11:44 -05:00
Bader Boland 56bba70ef3 Add ability to exclude individual tests 2020-01-24 08:53:34 -05:00
Robert Brennan 03cc3fb1bd Merge pull request #236 from FairwindsOps/rb/custom-checks-3
Major Refactor
2020-01-14 10:02:47 -05:00
Robert Brennan bcc1cc5384 remove unused code 2020-01-14 14:50:35 +00:00
Robert Brennan 5ef97ae896 fix lint errors 2020-01-14 14:50:35 +00:00
Robert Brennan dfa34e1880 explicitly handle schema validation errors 2020-01-14 14:50:35 +00:00
Robert Brennan 542694841a change resolveCheck signature 2020-01-14 14:50:35 +00:00
Robert Brennan e6a44c6ff8 formatting change 2020-01-14 14:50:35 +00:00
Robert Brennan 7637108234 refactor ValidateContainers 2020-01-14 14:50:34 +00:00
Robert Brennan 23bf4c81b0 refactor ValidatePod, add NakedPod type 2020-01-14 14:50:34 +00:00
Robert Brennan 090700f641 minor tweaks 2020-01-14 14:50:34 +00:00
Robert Brennan e129ba6971 fix dashboard after output change 2020-01-14 14:50:34 +00:00
Robert Brennan 51cd3523fc messages -> results 2020-01-14 14:50:34 +00:00
Robert Brennan ac501a20d2 add sample output 2020-01-14 14:50:34 +00:00
Robert Brennan 83a7448c99 add namespaces to controllers 2020-01-14 14:50:34 +00:00
Robert Brennan d228acfe71 add object meta to controller interface 2020-01-14 14:50:34 +00:00
Robert Brennan 49c540e993 type -> kind 2020-01-14 14:50:34 +00:00
Robert Brennan 9f7caabef4 change message type to boolean 2020-01-14 14:50:34 +00:00
Robert Brennan 2770be643f Refactor validation 2020-01-14 14:50:34 +00:00
Robert Brennan 5cd52242c4 Merge pull request #238 from FairwindsOps/rb/fix-full-config
fix config-full.yaml
2020-01-13 12:34:54 -05:00
Robert Brennan 6b4227da4d fix config-full.yaml 2020-01-13 16:01:36 +00:00
Robert Brennan 2e1aa0db4a Merge pull request #235 from FairwindsOps/rb/custom-checks-2
Implement custom checks
2020-01-09 08:32:31 -05:00
Robert Brennan e7baeac2e5 add script for generating deploy files 2020-01-03 18:32:29 +00:00
Robert Brennan 051319fab7 add logs to webhook test 2020-01-03 18:07:54 +00:00
Robert Brennan 917e630697 move some tests 2020-01-02 20:49:51 +00:00
Robert Brennan 68166559c5 expand docs 2020-01-02 20:21:46 +00:00
Robert Brennan fee55ba7f8 fix lint errors 2020-01-02 20:17:56 +00:00
Robert Brennan d0e0009efc revert dashboard.yaml 2020-01-02 20:10:22 +00:00
Robert Brennan 57ff1d5c06 remove messages 2020-01-02 20:07:55 +00:00
Robert Brennan f0834d30a2 update docs 2020-01-02 19:55:11 +00:00
Robert Brennan 4d82220212 fix up config-full 2020-01-02 19:26:03 +00:00
Robert Brennan f8d5ce70cf support schemas that use json strings 2020-01-02 18:59:11 +00:00
Robert Brennan 5f060801ef remove unused config structs 2020-01-02 17:58:22 +00:00
Robert Brennan 04da47d83e change input config to simplify things 2020-01-02 17:55:21 +00:00
Robert Brennan 5efa416ea9 implement custom checks, implement resource ranges as custom check 2020-01-02 17:55:21 +00:00
Robert Brennan 7b0fe81d01 implement capabilities checks in JSON schema 2020-01-02 17:55:21 +00:00
Robert Brennan 1067255173 Merge pull request #234 from FairwindsOps/rb/custom-checks
JSON schema checks
2020-01-02 12:55:03 -05:00
Robert Brennan 7cc0be4188 remove unused function 2019-12-23 21:16:59 +00:00
Robert Brennan b0035158d2 fix lint errors 2019-12-23 20:57:48 +00:00
Robert Brennan 7070eb724c Merge branch 'rb/custom-checks' of ssh://github.com/fairwindsops/polaris into rb/custom-checks 2019-12-23 20:32:45 +00:00
Robert Brennan f7dccc079b move more security checks to jsonschema 2019-12-23 20:32:38 +00:00
Robert Brennan 6c588848ef fix up exclusions 2019-12-23 20:32:38 +00:00
Robert Brennan ad3a8e6748 move runAsRootAllowed over to jsonschema 2019-12-23 20:32:38 +00:00
Robert Brennan 3fa627a2cd move networking checks over to json schema 2019-12-23 20:32:38 +00:00
Robert Brennan 30b49c4d7b implement image checks using json schema 2019-12-23 20:32:38 +00:00
Robert Brennan f2c5752718 migrate health checks to schemas 2019-12-23 20:32:38 +00:00
Robert Brennan d0dc7f4b0e simplify GetSupportedControllerFromString 2019-12-23 20:32:38 +00:00
Robert Brennan 3304285b4e move rest of pod checks over to schema 2019-12-23 20:32:38 +00:00
Robert Brennan d80d326f7c swap out host_network for a schema-based check 2019-12-23 20:32:38 +00:00
Robert Brennan df48615a26 Merge branch 'master' into rb/custom-checks 2019-12-23 15:31:40 -05:00
Robert Brennan 59785196d4 circleci: set env for go mod (#233)
* circleci: set env for go mod

* fix dockerfile to support go mod

* update go mod

* update go in docker
2019-12-23 15:31:24 -05:00
Robert Brennan 02252c690d move more security checks to jsonschema 2019-12-23 19:26:43 +00:00
Robert Brennan 25be9e41dd fix up exclusions 2019-12-23 19:03:37 +00:00
Robert Brennan ddf815d636 move runAsRootAllowed over to jsonschema 2019-12-23 18:59:26 +00:00
Robert Brennan 95c04b1e9d move networking checks over to json schema 2019-12-23 17:04:44 +00:00
Robert Brennan 0f2e5e0def implement image checks using json schema 2019-12-23 16:19:57 +00:00
Robert Brennan 8b20fd9dcf migrate health checks to schemas 2019-12-20 21:37:58 +00:00
Robert Brennan d4e3258d53 simplify GetSupportedControllerFromString 2019-12-20 20:14:49 +00:00
Robert Brennan b4e3f78b72 move rest of pod checks over to schema 2019-12-20 17:33:52 +00:00
Robert Brennan 20257cd917 swap out host_network for a schema-based check 2019-12-19 21:25:01 +00:00
Robert Brennan 317dfcefd2 update go.sum 2019-12-19 17:58:42 +00:00
Robert Brennan 42e8666356 add envrc 2019-12-19 17:53:23 +00:00
Robert Brennan 49a419548f move from gopkg to go mod 2019-12-19 17:53:12 +00:00
Robert Brennan 790607c6c0 remove vendor 2019-12-19 17:52:35 +00:00
Robert Brennan 91378c3d92 Bump version to 0.6 (#231)
* bump version to 0.6.0

* update changelog
2019-12-09 12:20:15 -05:00
Robert Brennan 207297c11d move to using fairwinds quay repo (#230) 2019-12-09 08:51:17 -05:00
Robert Brennan 0d86096f09 remove extensionsv1beta1 reference to support 1.16 (#229) 2019-12-06 11:03:38 -05:00
Robert Brennan 67ab987f7e Add support for annotation-based exemptions (#227)
* add controllers_to_scan to example config-full

* add support for annotation-based exemptions

* fix lint errors

* add docs
2019-12-06 08:29:30 -05:00
Robert Brennan 97457d71c0 Full support for validating webhook, now that tests are in place (#226) 2019-12-04 14:12:13 -05:00
Andrew Suderman 1159a380ba Adding an exception for flannel being privileged to example config (#225) 2019-11-22 12:02:35 -07:00
Robert Brennan ca6aa76729 Add default exemptions (#220)
* Update config.yaml

* Update config.yaml

* add a couple more exemptions
2019-11-15 14:45:58 -05:00
Robert Brennan 7ce71f1632 bump to version 0.5.2 (#224) 2019-11-13 15:27:24 -05:00
Robert Brennan 98b47e0aeb Fix resource success messages (#223)
* add success messages when resources are set

* add tests
2019-11-13 14:07:32 -05:00
Robert Brennan 159c165468 bump version (#221) 2019-11-12 11:40:54 -05:00
Robert Brennan 4eeabb2c7f pass RunAsNonRoot if RunAsUser > 0 (#219) 2019-11-11 13:21:32 -05:00
Barnabas Makonda 7e35b03452 Merge pull request #218 from FairwindsOps/kind-exceptions
Checks exemption for kubehunter, polaris and goldilocks
2019-11-08 23:35:42 +02:00
MAKOSCAFEE c8e4f766a2 revert version upgrade for dashboard and webhook 2019-11-07 23:03:29 +02:00
MAKOSCAFEE 0ee39a4470 revert version upgrade 2019-11-07 23:01:47 +02:00
MAKOSCAFEE fc7c913122 update exemption rules and check controller name prefix 2019-11-07 19:16:06 +02:00
MAKOSCAFEE 3e15586597 add kind exception configurations 2019-11-07 15:37:13 +02:00
Robert Brennan 3d07ce8004 update to 0.5.0 (#205)
* update to 0.5.0

* update changelog

* incorporate changes to polaris chart
2019-11-06 13:57:18 -05:00
Robert Brennan 22ab851681 skip health checks for jobs, cronjobs, and initContainers (#216) 2019-11-06 13:31:17 -05:00
Robert BrennanandAndrew Suderman 152c876067 Update CODEOWNERS
Co-Authored-By: Andrew Suderman <andrew@sudermanjr.com>
2019-11-05 09:42:23 -07:00
Robert Brennan c10b2e0e55 Update CODEOWNERS 2019-11-05 09:42:23 -07:00
Robert Brennan 2b15f11d57 Add exemptions to config (#204)
* first pass at adding exemptions

* Update config.yaml

* make config_test more reliable

* add flag to disallow exemptions in dashboard

* add disallow-exemptions flag to CLI

* add comments

* fix exemptions flag

* fix alert on dashboard

* minor style changes
2019-10-23 17:14:03 -04:00
Robert Brennan b172f61c5f Remove Kim as codeowner (#214) 2019-10-16 08:39:45 -04:00
Adam Shannon 150b812371 pkg/dashboard: setup basePath as a path prefix in routing (#201) 2019-10-02 12:17:59 -04:00
Robert Brennan 434b1f604f Create capabilitiesAdded and capabilitiesDropped IDs (#207)
* ensure check IDs are unique

* create capabilitiesAdded and capabilitiesDropped check IDs
2019-10-02 08:51:47 -04:00
Robert Brennan 6e3ab067d7 skip kubernetes tests for forked PRs (#202) 2019-09-18 10:12:36 -04:00
Robert Brennan 2e44d151fe fix empty dashboard when load-audit-file not specified (#203) 2019-09-18 09:39:43 -04:00
Robert Brennan c91a85a08a add IDs to each check (#197) 2019-09-11 14:07:08 -04:00
Robert Brennan f8bb171209 audit deploy configs in CI/CD (#198) 2019-09-09 13:10:19 -04:00
Bobby Brennan f860c34d62 upgrade alpine and golang images (#193) 2019-08-22 16:08:13 -04:00
Will Ledingham b8422a93b7 Wl/stored audits (#188)
* added ability to run dashboard from a stored audit result file.

* added ability to run dashboard from a stored audit result file.

* more changes

* debugging

* de

* all working as anticipated locally

* .

* updated way of decoding YAML/JSON files.

* remmoved unneded code

* renamed fn names, moved logic around and cleaned up main.go

* deleted output files from weird places.

* deleted test file
2019-08-14 17:20:19 -04:00
Bobby Brennan 15b14555a6 sleep to ensure dashboard/webhook are ready (#189) 2019-08-14 09:17:42 -04:00
Will Ledingham 467ab945cd Wl/webhook test (#182)
* Added testing for webhook using KIND

* fixed branching error

* added tests to CircleCI

* added tests to CircleCI

* attempt to stop failing circleci test

* moved location of testing for webhook in CircleCI

* debugging

* more debugging

* .

* .

* added download of webhook to test

* ..

* ...

* corrected sleep comand

* .

* .

* code working now, clean up

* testing smaller sleep times

* increased sleep time

* responded to comments on github

* .

* debug

* more edits

* debugging second test failing.

* debugging

* tests doing opposite of what they should be debugging.

* debugging

* .

* .

* fixing error in installation of webhook.

* .

* timeout increase

* trying to install webhook

* .

* .

* webhook still not i installing properly

* ..

* ..

* add log message

* ..

* changed order of test_k8s, removed set -e

* ..

* namespace polaris

* .

* .......

* intial testing for new strategy.

* intial testing for new strategy.

* .

* ...

* final edits, working now

* fixed files, cleaned up logs, added more detail to webhook starting documentation.

* ?

* added test files for other controller types, adding testing for them in webhook_test.sh

* increased sleep time

* testing

* finally added tests for jobs

* changed while loop condition to include webhook.

* .

* lskdfsjkl

* sd

* lskfjlskj

* .

* final

* added timeout test for dashboard to try to aleviate error

* .

* .

* install the dashboard

* ...

* initial test for new kube_dashboard_test

* initial test for new kube_dashboard_test

* ?

* deleting unused code

* final change for dashboard test

* final
2019-08-09 16:00:55 -04:00
Bobby Brennan dddeeb3332 Update docs (#186) 2019-08-05 08:51:23 -04:00
Nick Huanca f03f97ab07 bump version (#184) 2019-08-01 09:55:40 -06:00
Nick Huanca 75f70352ba Additional Pod Controller Scans (#166)
**Changes**

- Refactored the way controllers work to be an interface
- Added configurable controllers to include in scans
- Added daemonsets, jobs and cronjobs in scans
- Added `ReplicationController` type controllers to the supported list
- Adjusted logic for failed YAML parsing to bubble up errors
- Added better logic for calculating summaries on cluster wide results
- Relocated responsibilities for counting types into validators vs spreading it around more packages
- Fixed bug where cronjob parsing was using wrong KIND
- Added fixtures for mocking new controller types
- Added example yamls to test scanning files
- Added functions to NamespacedResult(s) to reduce code complexity deep set iterations
- Refactored how results get added to namespacedresults so adding more later is easier
- Minor signature changes for interface implementing structs for controllers
2019-07-31 15:56:27 -06:00
Bobby Brennan 4aa360a933 Remove swapfile 2019-07-31 14:26:18 -04:00
Will Ledingham dce24a0a34 Merge pull request #181 from FairwindsOps/wl/config-URL
Wl/config url
2019-07-31 10:45:21 -04:00
Will Ledingham fae7a9fc17 Merge branch 'master' into wl/config-URL 2019-07-30 17:02:02 -04:00
Will Ledingham 79b0a00d5c more edits 2019-07-30 16:59:11 -04:00
Will Ledingham e4fe5ab085 more edits 2019-07-30 16:45:49 -04:00
Will Ledingham 2049698c8e small edit 2019-07-30 16:37:24 -04:00
Bobby Brennan 20bd32afb6 Rename ReactiveOps to Fairwinds (#180)
* Rename ReactiveOps to Fairwinds

* Rename ReactiveOps to Fairwinds
2019-07-30 15:29:09 -04:00
Will Ledingham c132f7ee51 more edits 2019-07-29 13:29:53 -04:00
Will Ledingham 10025764cd altered to fix tests on CircleCl. 2019-07-29 11:10:59 -04:00
Will Ledingham c8c0e07a22 altered to fix tests on CircleCl. 2019-07-29 11:09:33 -04:00
Will Ledingham b55f4717b1 typo 2019-07-29 10:44:01 -04:00
Will Ledingham f493c380fc Merge branch 'wl/config-URL' of https://github.com/FairwindsOps/polaris into wl/config-URL 2019-07-29 10:34:56 -04:00
Will Ledingham baa652197a Fixed error handling in ParseFile, added a test for parseFile where no Http server is configured 2019-07-29 10:33:27 -04:00
Will Ledingham a6525fc807 Merge branch 'master' into wl/config-URL 2019-07-25 16:37:30 -04:00
Will Ledingham e01c39230a cleaned up unnecessary comments 2019-07-25 16:34:59 -04:00
Will Ledingham 7b8b5fee4d expanded ParseFile function to include URL's and added a test for it. 2019-07-25 16:31:17 -04:00
Will Ledingham 819b77ae1a Pod tests (#177)
* add pod test

* add pod test 2

* update tests

* Added pod tests for configured Host IPC, Host PID, and Host Newtowrk.

* Fixed expected vs. actual mixup
2019-07-25 09:31:37 -04:00
Bobby Brennan edccf88ccd update fairwinds logo (#179) 2019-07-25 08:52:57 -04:00
13655 changed files with 8156 additions and 7110977 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
DOCKERFILE='Dockerfile'
EXTERNAL_REGISTRY_BASE_DOMAIN=quay.io
REPOSITORY_NAME=reactiveops/polaris
REPOSITORY_NAME=fairwinds/polaris
DOCKERTAG=${EXTERNAL_REGISTRY_BASE_DOMAIN}/${REPOSITORY_NAME}
if [[ -n $CI_TAG ]]; then
ADDITIONAL_DOCKER_TAG_VERSIONS=()
+61 -40
View File
@@ -15,10 +15,14 @@ references:
echo 'export CI_BUILD_NUM=$CIRCLE_BUILD_NUM' >> ${BASH_ENV}
echo 'export CI_TAG=$CIRCLE_TAG' >> ${BASH_ENV}
echo 'export PUSH_ALL_VERSION_TAGS=true' >> ${BASH_ENV}
echo 'export GOPROXY=https://proxy.golang.org' >> ${BASH_ENV}
echo 'export GO111MODULE=on' >> ${BASH_ENV}
install_k8s: &install_k8s
run:
name: Install K8s
command: |
sudo apt-get update
echo "Installing git and jq"
sudo apt-get install -yqq jq git
@@ -49,7 +53,6 @@ references:
name: Update Coverage
command: |
if [[ -z $CIRCLE_PR_NUMBER ]]; then
go test ./pkg/... -coverprofile=coverage.txt -covermode=count
bash <(curl -s https://codecov.io/bash)
else
echo "Skipping coverage for forked PR"
@@ -58,51 +61,51 @@ references:
run:
name: Test Dashboard
command: |
go run main.go --dashboard --dashboard-port 3000 --audit-path ./examples &
sleep 5
go run main.go dashboard --port 3000 --audit-path ./examples &
sleep 30
curl -f http://localhost:3000 > /dev/null
curl -f http://localhost:3000/health > /dev/null
curl -f http://localhost:3000/favicon.ico > /dev/null
curl -f http://localhost:3000/static/css/main.css > /dev/null
curl -f http://localhost:3000/results.json > /dev/null
curl -f http://localhost:3000/details/security > /dev/null
test_kube_dashboard: &test_kube_dashboard
test_k8s: &test_k8s
run:
name: Test Dashboard
name: Test Kubernetes Deployments
command: |
kubectl apply -f ./deploy/dashboard.yaml
sleep 10
kubectl get pods --namespace polaris
kubectl port-forward --namespace polaris svc/polaris-dashboard 3000:80 &
sleep 5
curl -f http://localhost:3000 > /dev/null
curl -f http://localhost:3000/health > /dev/null
curl -f http://localhost:3000/favicon.ico > /dev/null
curl -f http://localhost:3000/static/css/main.css > /dev/null
curl -f http://localhost:3000/results.json > /dev/null
curl -f http://localhost:3000/details/security > /dev/null
if [[ -z $CIRCLE_PR_NUMBER ]]; then
./test/webhook_test.sh
./test/kube_dashboard_test.sh
else
echo "Skipping Kubernetes tests for forked PR"
fi
# Release scripts
install_goreleaser: &install_goreleaser
run:
name: Install GoReleaser
command: |
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.94.0/goreleaser_amd64.deb
echo "8dbad6683d6fc9367e637e6eed8e01a0d63c9660 goreleaser.deb" | sha1sum -c
curl -fsSLo goreleaser.deb https://github.com/goreleaser/goreleaser/releases/download/v0.131.1/goreleaser_amd64.deb
echo "640790dcbfa864f26de4c26c2d491f293a64525c8c6641c5bbdec7136b38977e goreleaser.deb" | sha256sum -c -
sudo dpkg -i goreleaser.deb
rm goreleaser.deb
docker_build: &docker_build
run:
name: Docker login and build
command: |
docker-pull -f .circleci/build.config
docker-build -f .circleci/build.config
docker_build_and_push: &docker_build_and_push
run:
name: Docker login, build, and push
command: |
docker-pull -f .circleci/build.config
docker-build -f .circleci/build.config
if [[ -z $CIRCLE_PR_NUMBER ]]; then
docker login quay.io -u="reactiveops+circleci" -p="${quay_token}"
docker-push -f .circleci/build.config
else
echo "Skipping docker push for forked PR"
fi
docker login quay.io -u="${fairwinds_quay_user}" -p="${fairwinds_quay_token}"
docker-push -f .circleci/build.config
release_deploy_configs: &release_deploy_configs
run:
name: Release deploy configs
@@ -115,7 +118,16 @@ references:
jobs:
build:
docker:
- image: quay.io/reactiveops/ci-images:v8.0-stretch
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
- *set_environment_variables
- *docker_build
push:
docker:
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
@@ -129,38 +141,42 @@ jobs:
steps:
- checkout
- *install_k8s
- *test_kube_dashboard
- *test_k8s
test:
working_directory: /go/src/github.com/reactiveops/polaris/
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.13
steps:
- checkout
- *set_environment_variables
- run: go get -u golang.org/x/lint/golint
- run: go list ./... | grep -v vendor | xargs golint -set_exit_status
- run: go list ./... | grep -v vendor | xargs go vet
- run: go test ./pkg/... -coverprofile=coverage.txt -covermode=count
- run: go run main.go audit --audit-path ./deploy --set-exit-code-below-score 100 --set-exit-code-on-danger
- *update_coverage
- *test_binary_dashboard
release_binary:
working_directory: /go/src/github.com/reactiveops/polaris/
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.13
steps:
- checkout
- setup_remote_docker
- *set_environment_variables
- *install_goreleaser
- run: go get -u github.com/gobuffalo/packr/v2/packr2
- run: git checkout -- . # FIXME: the go get makes the directory dirty
- run: packr2
- run: goreleaser
- *release_deploy_configs
release_images:
working_directory: /go/src/github.com/reactiveops/polaris/
working_directory: /go/src/github.com/fairwindsops/polaris/
docker:
- image: quay.io/reactiveops/ci-images:v8.0-stretch
- image: quay.io/reactiveops/ci-images:v11.0-stretch
steps:
- checkout
- setup_remote_docker
@@ -169,22 +185,27 @@ jobs:
workflows:
version: 2
build:
jobs:
- test
- test_k8s:
# Ignore update-version branch, which changes deploy/ image references before the images are built
filters:
branches:
ignore: /.*\/update-version/
- build:
requires:
- test
- push:
context: org-global
# Allow using testing tags for testing circle test + build steps
requires:
- build
filters:
tags:
only: /^testing-.*/
branches:
ignore: /pull\/[0-9]+/
- test_k8s:
requires:
- push
filters:
branches:
ignore: /pull\/[0-9]+/
release:
jobs:
- release_binary:
-1
View File
@@ -1,5 +1,4 @@
deploy
examples
.gitignore
.git/*
Dockerfile
+3
View File
@@ -0,0 +1,3 @@
export GO111MODULE=on
export GOPROXY=https://proxy.golang.org
+2
View File
@@ -6,6 +6,7 @@
*.dylib
polaris
!cmd/polaris
!deploy/helm/polaris
# Test binary, build with `go test -c`
@@ -20,3 +21,4 @@ main
*-packr.go
dist
.vscode
+22 -21
View File
@@ -1,29 +1,30 @@
builds:
- env:
- CGO_ENABLED=0
binary: polaris
archive:
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brew:
github:
owner: reactiveops
name: homebrew-tap
folder: Formula
description: Open Source Best Practices for Kubernetes
test: |
system "#{bin}/polaris --version"
builds:
- id: watcher
env:
- CGO_ENABLED=0
- GO111MODULE=on
goarch:
- amd64
- arm
- arm64
- 386
goarm:
- 6
- 7
brews:
- name: polaris
github:
owner: FairwindsOps
name: homebrew-tap
folder: Formula
description: Open Source Best Practices for Kubernetes
test: |
system "#{bin}/polaris version"
+80 -8
View File
@@ -1,5 +1,77 @@
# x.x.x (next release)
# 1.2.1
* Update date on dashboard footer
# 1.2.0
* Add ability to audit a single workload
* Enable `pullPolicyAlways` by default
* Fix for finding parent resources
# 1.1.1
* Show controller checks on dashboard
* Fix for orphaned pods w/ controller checks
# 1.1.0
* Add namespace filter in UI
* Add priorityClass check
* Support reading from STDIN
* Ensure severity is set for all custom checks
* Support audit files which use \r or \r\n as newline character
* Add option to exempt an entire controller from checks via config file
* Fixed case where parent resources trigger error
* Fixed UI zero-state
# 1.0.3
* Fixed case where parent resources trigger error
* Fixed dashboard link when `--base-path` is set
# 1.0.2
* Fixed case where custom CRDs are not covered by RBAC
# 1.0.1
* Added ARM binaries to releases
# 1.0.0
## New Features
* Added support for custom checks using JSON Schema
* Added support for arbitrary controllers, rather than a pre-configured set
* removed support for `controllers_to_scan` in config
* Added the ability to exempt a particular controller from a particular check.
* Docker image now includes the default config
## Breaking Changes
* Breaking changes in both input and output formats. See [Examples](/examples) for examples of the new formats.
* removed config-level configuration for checks like max/min memory settings
* changed severity `error` to `danger`
* Breaking changes [to the CLI](/docs/usage.md#cli-options)
* CLI flag `--set-exit-code-on-error` is now `--set-exit-code-on-danger`
* Flags `--version`, `--dashboard`, `--webhook`, and `--audit` are now arguments
* Port flags are now just `--port`
# 0.6.0
* Fixed webhook support in Kubernetes 1.16
* this also removes support for 1.8
* Added support for exemptions via controller annotations
# 0.5.2
* Fixed missing success messages for resource requests/limits
# 0.5.1
* Added a few more exemptions
* Started checking exemptions based on controller name prefix
* `runAsUser != 0` now passes the `runAsNonRoot` check
# 0.5.0
* Added `--load-audit-file` flag to run the dashboard from an existing audit
* Added an `ID` field to each check in the output
* Skip health checks for jobs, cronjobs, initcontainers
* Added support for exemptions
* Fixed dashboard base path option
# 0.4.0
* Added additional Pod Controllers to scan PodSpec (`jobs`, `cronjobs`, `daemonsets`, `replicationcontrollers`)
# 0.3.1
* Changed dashboard branding to refer to new org name Fairwinds
@@ -7,7 +79,7 @@
* Added `--set-exit-code-on-error` and `--set-exit-code-below-score` flags to better support CI/CD
# 0.2.1
* [Fix](https://github.com/reactiveops/polaris/issues/146): Fixed logic on RunAsNonRoot check to incorporate settings in podSpec
* [Fix](https://github.com/FairwindsOps/polaris/issues/146): Fixed logic on RunAsNonRoot check to incorporate settings in podSpec
# 0.2.0
* Added `--output-format` flag for better CI/CD support
@@ -16,23 +88,23 @@
* Show error message if no kubeconfig is set
# 0.1.5
* [Fix](https://github.com/reactiveops/polaris/issues/125): ignore limits/requests for initContainers
* [Fix](https://github.com/reactiveops/polaris/issues/132): support custom base path
* [Fix](https://github.com/FairwindsOps/polaris/issues/125): ignore limits/requests for initContainers
* [Fix](https://github.com/FairwindsOps/polaris/issues/132): support custom base path
# 0.1.4
* [Fix](https://github.com/reactiveops/polaris/issues/116): details pages getting template errors
* [Fix](https://github.com/reactiveops/polaris/issues/114): support all auth providers
* [Fix](https://github.com/reactiveops/polaris/issues/112): Ignore readiness probe for initContainers
* [Fix](https://github.com/FairwindsOps/polaris/issues/116): details pages getting template errors
* [Fix](https://github.com/FairwindsOps/polaris/issues/114): support all auth providers
* [Fix](https://github.com/FairwindsOps/polaris/issues/112): Ignore readiness probe for initContainers
# 0.1.3
* [Fix](https://github.com/reactiveops/polaris/issues/109): dashboard not updating when running persistently
* [Fix](https://github.com/FairwindsOps/polaris/issues/109): dashboard not updating when running persistently
# 0.1.2
* Stored all third-party assets (e.g. Charts.js) to local files to support offline dashboard viewing
* Fix: custom configs in `ConfigMap` not respected
# 0.1.1
* [Fix](https://github.com/reactiveops/polaris/issues/93): missing `config.yaml` and dashboard assets in binary releases
* [Fix](https://github.com/FairwindsOps/polaris/issues/93): missing `config.yaml` and dashboard assets in binary releases
* Added some tests and better error handling
# 0.1.0
+1 -1
View File
@@ -1 +1 @@
* @bobby-brennan @endzyme @kimschles @robscott
* @rbren @makoscafee @jordandoig @baderbuddy
+11 -12
View File
@@ -7,7 +7,7 @@ Issues, whether bugs, tasks, or feature requests are essential for keeping Polar
This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). Please review this document before contributing to this project.
## Sign the CLA
Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/reactiveops/polaris).
Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/fairwindsops/polaris).
## Project Structure
@@ -15,7 +15,7 @@ Polaris is built on top of [controller-runtime](https://github.com/kubernetes-si
## Getting Started
We label issues with the ["good first issue" tag](https://github.com/reactiveops/polaris/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) if we believe they'll be a good starting point for new contributors. If you're interested in working on an issue, please start a conversation on that issue, and we can help answer any questions as they come up.
We label issues with the ["good first issue" tag](https://github.com/FairwindsOps/polaris/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) if we believe they'll be a good starting point for new contributors. If you're interested in working on an issue, please start a conversation on that issue, and we can help answer any questions as they come up.
## Setting Up Your Development Environment
### Prerequisites
@@ -23,10 +23,10 @@ We label issues with the ["good first issue" tag](https://github.com/reactiveops
* If you want to see the local changes you make on a Polaris dashboard, you will need access to a Kubernetes cluster defined in `~/.kube/config`
### Installation
* Install the project with `go get github.com/reactiveops/polaris`
* Change into the polaris directory which is installed at `$GOPATH/src/github.com/reactiveops/polaris`
* See the dashboard with `go run main.go --dashboard`, then open http://localhost:8080/
* See the audit data `go run main.go --audit`. This command shows the audit information on the command line.
* Install the project with `go get github.com/fairwindsops/polaris`
* Change into the polaris directory which is installed at `$GOPATH/src/github.com/fairwindsops/polaris`
* See the dashboard with `go run main.go dashboard`, then open http://localhost:8080/
* See the audit data `go run main.go audit`. This command shows the audit information on the command line.
## Running Tests
@@ -76,12 +76,12 @@ it needs to be a minor or major release in order to prevent breaking the Helm ch
### Minor/Major releases
Minor and major releases need to change both this repository and the
[Helm chart repo](https://github.com/reactiveops/charts/).
[Helm chart repo](https://github.com/FairwindsOps/charts/).
The steps are:
1. Modify the [Helm chart](https://github.com/reactiveops/charts/stable/polaris)
1. Modify the [Helm chart](https://github.com/FairwindsOps/charts/stable/polaris)
1. Clone the helm charts repo
1. `git clone https://github.com/reactiveops/charts`
1. `git clone https://github.com/FairwindsOps/charts`
2. `git checkout -b yourname/update-polaris`
1. Bump the version number in:
1. stable/polaris/README.md
@@ -94,9 +94,8 @@ The steps are:
2. Bump the version number in:
1. main.go
2. README.md
3. Regenerate the deployment files. Assuming you've cloned the charts repo to `./charts`:
1. `helm template ./charts/stable/polaris/ --name polaris --namespace polaris --set templateOnly=true > deploy/dashboard.yaml`
2. `helm template ./charts/stable/polaris/ --name polaris --namespace polaris --set templateOnly=true --set webhook.enable=true --set dashboard.enable=false > deploy/webhook.yaml`
3. Regenerate the deployment files. Assuming you've cloned the charts repo to `~/git/charts`:
1. `CHARTS_DIR=~/git/charts ./scripts/generate-deployment-files.sh`
4. Update CHANGELOG.md
5. Merge your PR
3. Tag the latest branch for this repo
+16 -6
View File
@@ -1,17 +1,27 @@
FROM golang:1.12.4 AS build-env
WORKDIR /go/src/github.com/reactiveops/polaris/
FROM golang:1.13 AS build-env
WORKDIR /go/src/github.com/fairwindsops/polaris/
ENV GO111MODULE=on
ENV GOPROXY=https://proxy.golang.org
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go get -u github.com/gobuffalo/packr/v2/packr2
COPY . .
RUN go get -u github.com/gobuffalo/packr/v2/packr2
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 packr2 build -a -o polaris *.go
RUN packr2 build -a -o polaris *.go
FROM alpine:3.9
FROM alpine:3.10
WORKDIR /usr/local/bin
RUN apk --no-cache add ca-certificates
RUN addgroup -S polaris && adduser -u 1200 -S polaris -G polaris
USER 1200
COPY --from=build-env /go/src/github.com/reactiveops/polaris/polaris .
COPY --from=build-env /go/src/github.com/fairwindsops/polaris/polaris .
WORKDIR /opt/app
Generated
-512
View File
@@ -1,512 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "cloud.google.com/go"
packages = ["compute/metadata"]
revision = "775730d6e48254a2430366162cf6298e5368833c"
version = "v0.39.0"
[[projects]]
name = "contrib.go.opencensus.io/exporter/ocagent"
packages = ["."]
revision = "dcb33c7f3b7cfe67e8a2cea10207ede1b7c40764"
version = "v0.4.12"
[[projects]]
name = "github.com/Azure/go-autorest"
packages = ["autorest","autorest/adal","autorest/azure","autorest/date","logger","tracing"]
revision = "fe1ebaab71ae2a2ab8a55f62ebe54cffd842acc2"
version = "v12.0.0"
[[projects]]
name = "github.com/appscode/jsonpatch"
packages = ["."]
revision = "7c0e3b262f30165a8ec3d0b4c6059fd92703bfb2"
version = "1.0.0"
[[projects]]
name = "github.com/beorn7/perks"
packages = ["quantile"]
revision = "4b2b341e8d7715fae06375aa633dbb6e91b3fb46"
version = "v1.0.0"
[[projects]]
name = "github.com/census-instrumentation/opencensus-proto"
packages = ["gen-go/agent/common/v1","gen-go/agent/metrics/v1","gen-go/agent/trace/v1","gen-go/metrics/v1","gen-go/resource/v1","gen-go/trace/v1"]
revision = "a105b96453fe85139acc07b68de48f2cbdd71249"
version = "v0.2.0"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
name = "github.com/evanphx/json-patch"
packages = ["."]
revision = "5858425f75500d40c52783dce87d085a483ce135"
version = "v4.2.0"
[[projects]]
name = "github.com/go-logr/logr"
packages = ["."]
revision = "9fb12b3b21c5415d16ac18dc5cd42c1cfdd40c4e"
version = "v0.1.0"
[[projects]]
name = "github.com/go-logr/zapr"
packages = ["."]
revision = "03f06a783fbb7dfaf3f629c7825480e43a7105e6"
version = "v0.1.1"
[[projects]]
name = "github.com/gobuffalo/envy"
packages = ["."]
revision = "043cb4b8af871b49563291e32c66bb84378a60ac"
version = "v1.7.0"
[[projects]]
name = "github.com/gobuffalo/genny"
packages = ["."]
revision = "05dc5921e98daa5d406f90c2844ff163cb1e1c4e"
version = "v0.1.1"
[[projects]]
name = "github.com/gobuffalo/gogen"
packages = [".","goimports","gomods"]
revision = "5482e6ef5d999f8cfa9b038a53953c9b4ee090af"
version = "v0.1.1"
[[projects]]
branch = "master"
name = "github.com/gobuffalo/logger"
packages = ["."]
revision = "86e12af44bc273e0063fad5b6ad588890b8cfe6b"
[[projects]]
name = "github.com/gobuffalo/mapi"
packages = ["."]
revision = "0bb5e840be332d4280e40f2e6c50777c615bbac5"
version = "v1.0.2"
[[projects]]
name = "github.com/gobuffalo/packd"
packages = ["."]
revision = "9efbb667f2024a9370cadda45a57a0657c88a6fb"
version = "v0.1.0"
[[projects]]
name = "github.com/gobuffalo/packr"
packages = ["v2","v2/file","v2/file/resolver","v2/file/resolver/encoding/hex","v2/jam/parser","v2/plog"]
revision = "cda4ac25577350dac682c8715dc6659327232596"
version = "v2.2.0"
[[projects]]
branch = "master"
name = "github.com/gobuffalo/syncx"
packages = ["."]
revision = "33c29581e754bd354236e977dfe426e55331c45d"
[[projects]]
name = "github.com/gogo/protobuf"
packages = ["proto","sortkeys"]
revision = "ba06b47c162d49f2af050fb4c75bcbc86a159d5c"
version = "v1.2.1"
[[projects]]
branch = "master"
name = "github.com/golang/groupcache"
packages = ["lru"]
revision = "5b532d6fd5efaf7fa130d4e859a2fde0fc3a9e1b"
[[projects]]
name = "github.com/golang/protobuf"
packages = ["jsonpb","proto","protoc-gen-go/descriptor","protoc-gen-go/generator","protoc-gen-go/generator/internal/remap","protoc-gen-go/plugin","ptypes","ptypes/any","ptypes/duration","ptypes/struct","ptypes/timestamp","ptypes/wrappers"]
revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
version = "v1.3.1"
[[projects]]
name = "github.com/google/btree"
packages = ["."]
revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306"
version = "v1.0.0"
[[projects]]
name = "github.com/google/gofuzz"
packages = ["."]
revision = "f140a6486e521aad38f5917de355cbf147cc0496"
version = "v1.0.0"
[[projects]]
name = "github.com/google/uuid"
packages = ["."]
revision = "0cd6bf5da1e1c83f8b45653022c74f71af0538a4"
version = "v1.1.1"
[[projects]]
name = "github.com/googleapis/gnostic"
packages = ["OpenAPIv2","compiler","extensions"]
revision = "7c663266750e7d82587642f65e60bc4083f1f84e"
version = "v0.2.0"
[[projects]]
branch = "master"
name = "github.com/gophercloud/gophercloud"
packages = [".","openstack","openstack/identity/v2/tenants","openstack/identity/v2/tokens","openstack/identity/v3/tokens","openstack/utils","pagination"]
revision = "b3a23cc94cc51857a508733a0bd3d6ef0bfc16d9"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "ed099d42384823742bba0bf9a72b53b55c9e2e38"
version = "v1.7.2"
[[projects]]
branch = "master"
name = "github.com/gregjones/httpcache"
packages = [".","diskcache"]
revision = "3befbb6ad0cc97d4c25d851e9528915809e1a22f"
[[projects]]
name = "github.com/grpc-ecosystem/grpc-gateway"
packages = ["internal","runtime","utilities"]
revision = "8fd5fd9d19ce68183a6b0934519dfe7fe6269612"
version = "v1.9.0"
[[projects]]
name = "github.com/hashicorp/golang-lru"
packages = [".","simplelru"]
revision = "7087cb70de9f7a8bc0a10c375cb0d2280a8edf9c"
version = "v0.5.1"
[[projects]]
name = "github.com/imdario/mergo"
packages = ["."]
revision = "7c29201646fa3de8506f701213473dd407f19646"
version = "v0.3.7"
[[projects]]
name = "github.com/joho/godotenv"
packages = ["."]
revision = "23d116af351c84513e1946b527c88823e476be13"
version = "v1.3.0"
[[projects]]
name = "github.com/json-iterator/go"
packages = ["."]
revision = "0ff49de124c6f76f8494e194af75bde0f1a49a29"
version = "v1.1.6"
[[projects]]
name = "github.com/karrick/godirwalk"
packages = ["."]
revision = "5e617d9cfec6f870e63b168e9a2fb7c490a1c108"
version = "v1.10.3"
[[projects]]
name = "github.com/konsorten/go-windows-terminal-sequences"
packages = ["."]
revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
version = "v1.0.2"
[[projects]]
branch = "master"
name = "github.com/markbates/oncer"
packages = ["."]
revision = "bf2de49a0be218916e69a11d22866e6cd0a560f2"
[[projects]]
name = "github.com/markbates/safe"
packages = ["."]
revision = "6fea05a5732486546a4836b7a1d596c5ec687b98"
version = "v1.0.1"
[[projects]]
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
version = "v1.0.1"
[[projects]]
name = "github.com/modern-go/concurrent"
packages = ["."]
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
version = "1.0.3"
[[projects]]
name = "github.com/modern-go/reflect2"
packages = ["."]
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
version = "1.0.1"
[[projects]]
name = "github.com/pborman/uuid"
packages = ["."]
revision = "adf5a7427709b9deb95d29d3fa8a2bf9cfd388f1"
version = "v1.2"
[[projects]]
branch = "master"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
revision = "33fb24c13b99c46c93183c291836c573ac382536"
[[projects]]
name = "github.com/peterbourgon/diskv"
packages = ["."]
revision = "0be1b92a6df0e4f5cb0a5d15fb7f643d0ad93ce6"
version = "v3.0.0"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
version = "v0.8.1"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/prometheus/client_golang"
packages = ["prometheus","prometheus/internal","prometheus/promhttp"]
revision = "50c4339db732beb2165735d2cde0bff78eb3c5a5"
version = "v0.9.3"
[[projects]]
branch = "master"
name = "github.com/prometheus/client_model"
packages = ["go"]
revision = "fd36f4220a901265f90734c3183c5f0c91daa0b8"
[[projects]]
name = "github.com/prometheus/common"
packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"]
revision = "1ba88736f028e37bc17328369e94a537ae9e0234"
version = "v0.4.0"
[[projects]]
branch = "master"
name = "github.com/prometheus/procfs"
packages = [".","internal/fs"]
revision = "9935e8e0588d26307d1d4a07bed7bd47271b6bbd"
[[projects]]
name = "github.com/rogpeppe/go-internal"
packages = ["modfile","module","semver"]
revision = "438578804ca6f31be148c27683afc419ce47c06e"
version = "v1.3.0"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
version = "v1.4.2"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
version = "v1.0.3"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]]
branch = "master"
name = "gitlab.com/golang-commonmark/html"
packages = ["."]
revision = "cfaf75183c4a5f0e44e3d51ece1065accafdd2b3"
[[projects]]
branch = "master"
name = "gitlab.com/golang-commonmark/linkify"
packages = ["."]
revision = "c22b7bdb117955ff2cc0e3724083d35e3419d71e"
[[projects]]
name = "gitlab.com/golang-commonmark/markdown"
packages = ["."]
revision = "772775880e1fbf39a7804149ed11c0f714ec89fe"
[[projects]]
branch = "master"
name = "gitlab.com/golang-commonmark/mdurl"
packages = ["."]
revision = "e5bce34c34f29ecf232fec34f3982e55533ec14e"
[[projects]]
branch = "master"
name = "gitlab.com/golang-commonmark/puny"
packages = ["."]
revision = "2cd490539afe7c6fc0eda6c59ef88fa93a00ea0d"
[[projects]]
name = "go.opencensus.io"
packages = [".","internal","internal/tagencoding","metric/metricdata","metric/metricproducer","plugin/ocgrpc","plugin/ochttp","plugin/ochttp/propagation/b3","plugin/ochttp/propagation/tracecontext","resource","stats","stats/internal","stats/view","tag","trace","trace/internal","trace/propagation","trace/tracestate"]
revision = "43463a80402d8447b7fce0d2c58edf1687ff0b58"
version = "v0.19.3"
[[projects]]
name = "go.uber.org/atomic"
packages = ["."]
revision = "df976f2515e274675050de7b3f42545de80594fd"
version = "v1.4.0"
[[projects]]
name = "go.uber.org/multierr"
packages = ["."]
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
version = "v1.1.0"
[[projects]]
name = "go.uber.org/zap"
packages = [".","buffer","internal/bufferpool","internal/color","internal/exit","zapcore"]
revision = "27376062155ad36be76b0f12cf1572a221d3a48c"
version = "v1.10.0"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
revision = "22d7a77e9e5f409e934ed268692e56707cd169e5"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","context/ctxhttp","http/httpguts","http2","http2/hpack","idna","internal/timeseries","trace"]
revision = "3ec19112720433827bbce8be9342797f5a6aaaf9"
[[projects]]
branch = "master"
name = "golang.org/x/oauth2"
packages = [".","google","internal","jws","jwt"]
revision = "950ef44c6e079baf075030377d90bf0c7e4b7b7a"
[[projects]]
branch = "master"
name = "golang.org/x/sync"
packages = ["semaphore"]
revision = "112230192c580c3556b8cee6403af37a4fc5f28c"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "ad400b1274690a55531a013560dc08706088f82b"
[[projects]]
name = "golang.org/x/text"
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/language","internal/language/compact","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
version = "v0.3.2"
[[projects]]
branch = "master"
name = "golang.org/x/time"
packages = ["rate"]
revision = "9d24e82272b4f38b78bc8cff74fa936d31ccd8ef"
[[projects]]
branch = "master"
name = "golang.org/x/tools"
packages = ["go/ast/astutil","go/gcexportdata","go/internal/gcimporter","go/internal/packagesdriver","go/packages","go/types/typeutil","imports","internal/fastwalk","internal/gopathwalk","internal/module","internal/semver"]
revision = "d88f79806bbd013f54a668506864ce559edf6f0a"
[[projects]]
name = "google.golang.org/api"
packages = ["support/bundler"]
revision = "721295fe20d585ce7e948146f82188429d14da33"
version = "v0.5.0"
[[projects]]
name = "google.golang.org/appengine"
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "4c25cacc810c02874000e4f7071286a8e96b2515"
version = "v1.6.0"
[[projects]]
branch = "master"
name = "google.golang.org/genproto"
packages = ["googleapis/api/httpbody","googleapis/rpc/status","protobuf/field_mask"]
revision = "bb713bdc0e5239f2b68e560efbe1c701a6fe78f9"
[[projects]]
name = "google.golang.org/grpc"
packages = [".","balancer","balancer/base","balancer/roundrobin","binarylog/grpc_binarylog_v1","codes","connectivity","credentials","credentials/internal","encoding","encoding/proto","grpclog","internal","internal/backoff","internal/balancerload","internal/binarylog","internal/channelz","internal/envconfig","internal/grpcrand","internal/grpcsync","internal/syscall","internal/transport","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap"]
revision = "25c4f928eaa6d96443009bd842389fb4fa48664e"
version = "v1.20.1"
[[projects]]
name = "gopkg.in/inf.v0"
packages = ["."]
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
version = "v0.9.1"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
[[projects]]
name = "k8s.io/api"
packages = ["admission/v1beta1","admissionregistration/v1alpha1","admissionregistration/v1beta1","apps/v1","apps/v1beta1","apps/v1beta2","auditregistration/v1alpha1","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","autoscaling/v2beta2","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","coordination/v1beta1","core/v1","events/v1beta1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","scheduling/v1beta1","settings/v1alpha1","storage/v1","storage/v1alpha1","storage/v1beta1"]
revision = "05914d821849570fba9eacfb29466f2d8d3cd229"
version = "kubernetes-1.13.1"
[[projects]]
name = "k8s.io/apimachinery"
packages = ["pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apis/meta/internalversion","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1beta1","pkg/conversion","pkg/conversion/queryparams","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/cache","pkg/util/clock","pkg/util/diff","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/mergepatch","pkg/util/naming","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/strategicpatch","pkg/util/uuid","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/json","third_party/forked/golang/reflect"]
revision = "2b1284ed4c93a43499e781493253e2ac5959c4fd"
version = "kubernetes-1.13.0"
[[projects]]
name = "k8s.io/client-go"
packages = ["discovery","discovery/fake","dynamic","kubernetes","kubernetes/fake","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1alpha1/fake","kubernetes/typed/admissionregistration/v1beta1","kubernetes/typed/admissionregistration/v1beta1/fake","kubernetes/typed/apps/v1","kubernetes/typed/apps/v1/fake","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta1/fake","kubernetes/typed/apps/v1beta2","kubernetes/typed/apps/v1beta2/fake","kubernetes/typed/auditregistration/v1alpha1","kubernetes/typed/auditregistration/v1alpha1/fake","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1/fake","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authentication/v1beta1/fake","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1/fake","kubernetes/typed/authorization/v1beta1","kubernetes/typed/authorization/v1beta1/fake","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v1/fake","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta1/fake","kubernetes/typed/autoscaling/v2beta2","kubernetes/typed/autoscaling/v2beta2/fake","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1/fake","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v1beta1/fake","kubernetes/typed/batch/v2alpha1","kubernetes/typed/batch/v2alpha1/fake","kubernetes/typed/certificates/v1beta1","kubernetes/typed/certificates/v1beta1/fake","kubernetes/typed/coordination/v1beta1","kubernetes/typed/coordination/v1beta1/fake","kubernetes/typed/core/v1","kubernetes/typed/core/v1/fake","kubernetes/typed/events/v1beta1","kubernetes/typed/events/v1beta1/fake","kubernetes/typed/extensions/v1beta1","kubernetes/typed/extensions/v1beta1/fake","kubernetes/typed/networking/v1","kubernetes/typed/networking/v1/fake","kubernetes/typed/policy/v1beta1","kubernetes/typed/policy/v1beta1/fake","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1/fake","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1alpha1/fake","kubernetes/typed/rbac/v1beta1","kubernetes/typed/rbac/v1beta1/fake","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1alpha1/fake","kubernetes/typed/scheduling/v1beta1","kubernetes/typed/scheduling/v1beta1/fake","kubernetes/typed/settings/v1alpha1","kubernetes/typed/settings/v1alpha1/fake","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1/fake","kubernetes/typed/storage/v1alpha1","kubernetes/typed/storage/v1alpha1/fake","kubernetes/typed/storage/v1beta1","kubernetes/typed/storage/v1beta1/fake","pkg/apis/clientauthentication","pkg/apis/clientauthentication/v1alpha1","pkg/apis/clientauthentication/v1beta1","pkg/version","plugin/pkg/client/auth","plugin/pkg/client/auth/azure","plugin/pkg/client/auth/exec","plugin/pkg/client/auth/gcp","plugin/pkg/client/auth/oidc","plugin/pkg/client/auth/openstack","rest","rest/watch","restmapper","testing","third_party/forked/golang/template","tools/auth","tools/cache","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/leaderelection","tools/leaderelection/resourcelock","tools/metrics","tools/pager","tools/record","tools/reference","transport","util/buffer","util/cert","util/connrotation","util/flowcontrol","util/homedir","util/integer","util/jsonpath","util/retry","util/workqueue"]
revision = "8d9ed539ba3134352c586810e749e58df4e94e4f"
version = "kubernetes-1.13.1"
[[projects]]
name = "k8s.io/klog"
packages = ["."]
revision = "e531227889390a39d9533dde61f590fe9f4b0035"
version = "v0.3.0"
[[projects]]
branch = "master"
name = "k8s.io/kube-openapi"
packages = ["pkg/util/proto"]
revision = "a01b7d5d6c2258c80a4a10070f3dee9cd575d9c7"
[[projects]]
name = "sigs.k8s.io/controller-runtime"
packages = ["pkg/cache","pkg/cache/internal","pkg/client","pkg/client/apiutil","pkg/client/config","pkg/internal/recorder","pkg/leaderelection","pkg/manager","pkg/metrics","pkg/patch","pkg/recorder","pkg/runtime/inject","pkg/runtime/log","pkg/runtime/signals","pkg/webhook","pkg/webhook/admission","pkg/webhook/admission/builder","pkg/webhook/admission/types","pkg/webhook/internal/cert","pkg/webhook/internal/cert/generator","pkg/webhook/internal/cert/writer","pkg/webhook/internal/cert/writer/atomic","pkg/webhook/internal/metrics","pkg/webhook/types"]
revision = "12d98582e72927b6cd0123e2b4e819f9341ce62c"
version = "v0.1.10"
[[projects]]
name = "sigs.k8s.io/yaml"
packages = ["."]
revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"
version = "v1.1.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "205aba713edfc3b46886718dc86dbc24edc9e9758efd16724c2cdaa4256e6d50"
solver-name = "gps-cdcl"
solver-version = 1
-62
View File
@@ -1,62 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.3.0"
[[constraint]]
name = "k8s.io/api"
version = "kubernetes-1.13.1"
[[constraint]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.13.1"
[[constraint]]
name = "k8s.io/client-go"
version = "kubernetes-1.13.1"
[[constraint]]
name = "sigs.k8s.io/controller-runtime"
version = "0.1.10"
[[constraint]]
name = "github.com/gobuffalo/packr"
version = "2.2.0"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.7.1"
[[constraint]]
revision = "772775880e1fbf39a7804149ed11c0f714ec89fe"
name = "gitlab.com/golang-commonmark/markdown"
[[constraint]]
revision = "v1.3.1"
name = "github.com/golang/protobuf"
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2019 ReactiveOps
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.
+37 -156
View File
@@ -1,11 +1,11 @@
<div align="center">
<img src="/pkg/dashboard/assets/images/polaris-logo.png" alt="Polaris Logo" />
<img src="/img/polaris-logo.png" alt="Polaris Logo" />
<br>
[![Version][version-image]][version-link] [![CircleCI][circleci-image]][circleci-link] [![Go Report Card][goreport-image]][goreport-link]
</div>
[version-image]: https://img.shields.io/static/v1.svg?label=Version&message=0.3.1&color=239922
[version-image]: https://img.shields.io/static/v1.svg?label=Version&message=1.2.0&color=239922
[version-link]: https://github.com/FairwindsOps/polaris
[goreport-image]: https://goreportcard.com/badge/github.com/FairwindsOps/polaris
@@ -14,190 +14,71 @@
[circleci-image]: https://circleci.com/gh/FairwindsOps/polaris.svg?style=svg
[circleci-link]: https://circleci.com/gh/FairwindsOps/polaris.svg
Polaris helps keep your cluster healthy. It runs a variety of checks to ensure that
Kubernetes deployments are configured using best practices, helping you avoid
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:
- A dashboard that provides an overview of how well current deployments are configured within a cluster.
- An experimental validating webhook that can prevent any future deployments that do not live up to a configured standard.
- A command-line audit that can be incorporated into your CI/CD pipeline
Polaris can be run in three different modes:
* As a [dashboard](#dashboard), so you can audit what's running inside your cluster.
* As a [validating webhook](#webhook), so you can automatically reject workloads that don't adhere to your organization's policies.
* As a [command-line tool](#cli), so you can test local YAML files, e.g. as part of a CI/CD process.
**Want to learn more?** ReactiveOps holds [office hours on Zoom](https://zoom.us/j/242508205) the first Friday of every month, at 12pm Eastern. You can also reach out via email at `opensource@fairwinds.com`
**Want to learn more?** Reach out on [the Slack channel](https://fairwindscommunity.slack.com/messages/polaris) ([request invite](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)), send an email to `opensource@fairwinds.com`, or join us for [office hours on Zoom](https://fairwindscommunity.slack.com/messages/office-hours)
## Quickstart
# Dashboard Quickstart
```
kubectl apply -f https://github.com/reactiveops/polaris/releases/latest/download/dashboard.yaml
```bash
kubectl apply -f https://github.com/FairwindsOps/polaris/releases/latest/download/dashboard.yaml
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
With the port forwarding in place, you can open http://localhost:8080 in your browser to view the dashboard.
## Dashboard
* * *
The Polaris dashboard is a way to get a simple visual overview of the current state of your Kubernetes deployments as well as a roadmap for what can be improved. The dashboard provides a cluster wide overview as well as breaking out results by category, namespace, and deployment.
# Components
## Dashboard
> [View installation instructions](docs/usage.md#dashboard)
The Polaris dashboard is a way to get a simple visual overview of the current state of your Kubernetes workloads as well as a roadmap for what can be improved. The dashboard provides a cluster wide overview as well as breaking out results by category, namespace, and workload.
<p align="center">
<img src="/dashboard-screenshot.png" alt="Polaris Dashboard" />
<img src="/img/dashboard-screenshot.png" alt="Polaris Dashboard" width="550"/>
</p>
Our default standards in Polaris are rather high, so dont be surprised if your score is lower than you might expect. A key goal for Polaris was to set a high standard and aim for great configuration by default. If the defaults weve included are too strict, its easy to adjust the configuration as part of the deployment configuration to better suit your workloads.
## Webhook
Polaris includes experimental support for an optional validating webhook. This accepts the same configuration as the dashboard, and can run the same validations. This webhook will reject any deployments that trigger a validation error. This is indicative of the greater goal of Polaris, not just to encourage better configuration through dashboard visibility, but to actually enforce it with this webhook. *Although we are working towards greater stability and better test coverage, we do not currently consider this webhook component production ready.*
## Admission Controller: Validating Webhook
> [View installation instructions](docs/usage.md#webhook)
Unfortunately we have not found a way to display warnings as part of `kubectl` output unless we are rejecting a deployment altogether. That means that any checks with a severity of `warning` will still pass webhook validation, and the only evidence of that warning will either be in the Polaris dashboard or the Polaris webhook logs.
Polaris can be run as an admission controller that acts as a validating webhook. This accepts the same configuration as the dashboard, and can run the same validations. This webhook will reject any workloads that trigger a danger-level check. This is indicative of the greater goal of Polaris, not just to encourage better configuration through dashboard visibility, but to actually enforce it with this webhook. Polaris will not fix your workloads, only block them.
## Installation and Usage
Polaris can be installed on your cluster using kubectl or Helm. It can also
be run as a local binary, which will use your kubeconfig to connect to the cluster
or run against local YAML files.
The webhook only supports a fixed set of controller types. You can see the
[list of supported controllers here](https://github.com/FairwindsOps/polaris/blob/master/cmd/polaris/webhook.go#L40)
### kubectl
#### Dashboard
```
kubectl apply -f https://github.com/reactiveops/polaris/releases/latest/download/dashboard.yaml
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
Unfortunately we have not found a way to display warnings as part of `kubectl` output unless we are rejecting a workload altogether. That means that any checks with a severity of `warning` will still pass webhook validation, and the only evidence of that warning will either be in the Polaris dashboard or the Polaris webhook logs.
#### Webhook
```
kubectl apply -f https://github.com/reactiveops/polaris/releases/latest/download/webhook.yaml
```
## CLI
> [View installation instructions](docs/usage.md#cli)
### Helm
Start by adding the ReactiveOps Helm repo:
```
helm repo add reactiveops-stable https://charts.reactiveops.com/stable
```
Polaris can also be used on the command line, either to audit local files or a running cluster.
This is particularly helpful for running Polaris against your infrastructure-as-code as part of a
CI/CD pipeline. Use the available [command line flags](docs/usage.md#running-with-ci-cd)
to cause CI/CD to fail if your Polaris score drops below a certain threshold, or if any danger-level issues arise.
#### Dashboard
```
helm upgrade --install polaris reactiveops-stable/polaris --namespace polaris
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
# Installation and Usage
See the [Usage Guide](/docs/usage.md) for details on different methods for installing and using Polaris.
#### Webhook
```
helm upgrade --install polaris reactiveops-stable/polaris --namespace polaris \
--set webhook.enable=true --set dashboard.enable=false
```
### Local Binary
#### Installation
Binary releases are available on the [releases page](https://github.com/reactiveops/polaris/releases) or can be installed with [Homebrew](https://brew.sh/):
```
brew tap reactiveops/tap
brew install reactiveops/tap/polaris
polaris --version
```
You can run `polaris --help` to see a full list of options.
#### Dashboard
The dashboard can be run on your local machine, without installing anything on the cluster.
Polaris will use your local kubeconfig to connect to the cluster.
```
polaris --dashboard --dashboard-port 8080
```
#### Audits
You can also run audits on the command line and see the output as JSON, YAML, or a raw score:
```
polaris --audit --output-format yaml > report.yaml
polaris --audit --output-format score
# 92
```
Both the dashboard and audits can run against a local directory or YAML file
rather than a cluster:
```
polaris --audit --audit-path ./deploy/
```
##### Running with CI/CD
You can integrate Polaris into CI/CD for repositories containing infrastructure-as-code.
For example, to fail if polaris detects *any* error-level issues, or if the score drops below 90%:
```bash
polaris --audit --audit-path ./deploy/ \
--set-exit-code-on-error \
--set-exit-code-below-score 90
```
## Configuration
Polaris supports a wide range of validations covering a number of Kubernetes best practices. Here's a sample configuration file that includes all currently supported checks. The [default configuration](https://github.com/reactiveops/polaris/blob/master/examples/config.yaml) contains a number of those checks. This repository also includes a sample [full configuration file](https://github.com/reactiveops/polaris/blob/master/examples/config-full.yaml) that enables all available checks.
Each check can be assigned a `severity`. Only checks with a severity of `error` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `error` will result in a change being rejected.
Polaris validation checks fall into several different categories:
- [Health Checks](docs/health-checks.md)
- [Images](docs/images.md)
- [Networking](docs/networking.md)
- [Resources](docs/resources.md)
- [Security](docs/security.md)
## CLI Options
```
# high-level flags
-version
Prints the version of Polaris
-config string
Location of Polaris configuration file
-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
-dashboard
Runs the webserver for Polaris dashboard.
-dashboard-base-path string
Path on which the dashboard is served (default "/")
-dashboard-port int
Port for the dashboard webserver (default 8080)
-display-name string
An optional identifier for the audit
# audit flags
-audit
Runs a one-time audit.
-audit-path string
If specified, audits one or more YAML files instead of a cluster
-output-file string
Destination file for audit results
-output-format string
Output format for results - json, yaml, or score (default "json")
-output-url string
Destination URL to send audit results
-set-exit-code-below-score int
When running with --audit, set an exit code of 4 when the score is below this threshold (1-100)
-set-exit-code-on-error
When running with --audit, set an exit code of 3 when the audit contains error-level issues.
# webhook flags
-webhook
Runs the webhook webserver.
-webhook-port int
Port for the webhook webserver (default 9876)
-disable-webhook-config-installer
disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
```
## Contributing
# Contributing
PRs welcome! Check out the [Contributing Guidelines](CONTRIBUTING.md),
[Code of Conduct](CODE_OF_CONDUCT.md), and [Roadmap](ROADMAP.md) for more information.
## Further Information
# Further Information
A history of changes to this project can be viewed in the [Changelog](CHANGELOG.md)
If you'd like to learn more about Polaris, or if you'd like to speak with
a Kubernetes expert, you can contact `info@fairwinds.com` or [visit our website](https://fairwinds.com)
## License
# License
Apache License 2.0
+16 -50
View File
@@ -5,55 +5,21 @@ as well as the project's overall functionality.
If you have ideas for a new check, or for new Polaris features,
you can submit a pull request for this file, or open an issue in GitHub.
## Q3 2019
Below is a list of work we plan to get done this quarter. Some more details can be found under
[Future Work](#future_work), or
[in the milestone](https://github.com/FairwindsOps/polaris/milestone/1)
* Rule exceptions - we'd like to provide a way of overriding Polaris checks on individual controllers, e.g. through annotations
* More controller types - we plan to implement checks for more than just deployments
* Image repo checks - we plan to provide a check that ensures all repos conform to a set of user-defined patterns
* OPA integration (investigate only) - we plan to investigate the potential use of OPA to let users define custom Polaris checks
## Future Work
### Validating Webhook
The validating webhook rejects incoming workloads if they cause `error`-level
issues, as defined by the Polaris configuration.
The webhook server is currently functional, but largely untested. Because
the validating webhook has the potential to cause headaches for users,
we have marked it as `beta` for now.
We will continue to gather feedback here and will move the webhook out of `beta`
when we feel it's ready.
#### TODO
* Pass `warning`-level messages back to `kubectl` if possible (may require changes
to core k8s or `kubectl`)
### Ability to override checks
We plan to add the ability to override Polaris checks for particular namespaces
and workloads. This may be something that is set in the Polaris configuration,
or an annotation that can be added to the resource definition.
This is important, as certain workloads have good reason to skip particular Polaris
checks - for instance, the `dns-controller` deployment in `kube-system` needs to have
the host network configured.
### Images Registries Check
We'd like to allow users to restrict images to a list of allowed/disallowed registries.
We'll likely allow patterns, e.g. `*.dkr.ecr.*.amazonaws.com`
### New controller types
Currently we only look at Deployments. We plan to also validate these types of controllers:
* StatefulSet
* DaemonSet
* Job
* CronJob
* ReplicaSet
### Check non-controllers
## Check non-controllers
We would like to implement checks for non-controller types, such as:
* Ingress
* Service
* Secret
* Ingresses
* Services
* RBAC roles/bindings
## Expand list of checks
We'd like to increase our library of checks. Not all checks need to be enabled by default,
so anything that would be useful across different organizations is worth checking in.
## Support more controllers in the validating webhook
Currently the webhook only checks for a fixed set of controllers.
While we can't listen for _all_ possible controller types, we can block Pods that violate policy.
We've chosen not to do this for now, because it could prevent existing controllers from scaling.
However, there may be a way to check for the owner's age, in order to determine if it's a new
controller or a pre-existing controller.
+27
View File
@@ -0,0 +1,27 @@
successMessage: CPU limits are set
failureMessage: CPU limits should be set
category: Resources
target: Container
containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- limits
properties:
limits:
type: object
required:
- cpu
properties:
cpu:
type: string
not:
const: ''
+27
View File
@@ -0,0 +1,27 @@
successMessage: CPU requests are set
failureMessage: CPU requests should be set
category: Resources
target: Container
containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- requests
properties:
requests:
type: object
required:
- cpu
properties:
cpu:
type: string
not:
const: ''
+25
View File
@@ -0,0 +1,25 @@
successMessage: Container does not have any dangerous capabilities
failureMessage: Container should not have dangerous capabilities
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
securityContext:
type: object
properties:
capabilities:
type: object
properties:
add:
type: array
not:
contains:
const: ALL
not:
contains:
const: SYS_ADMIN
not:
contains:
const: NET_ADMIN
+11
View File
@@ -0,0 +1,11 @@
successMessage: Host IPC is not configured
failureMessage: Host IPC should not be configured
category: Security
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
hostIPC:
not:
const: true
+11
View File
@@ -0,0 +1,11 @@
successMessage: Host network is not configured
failureMessage: Host network should not be configured
category: Networking
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
hostNetwork:
not:
const: true
+11
View File
@@ -0,0 +1,11 @@
successMessage: Host PID is not configured
failureMessage: Host PID should not be configured
category: Security
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
hostPID:
not:
const: true
+15
View File
@@ -0,0 +1,15 @@
successMessage: Host port is not configured
failureMessage: Host port should not be configured
category: Networking
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
properties:
ports:
type: array
items:
properties:
hostPort:
const: 0
+31
View File
@@ -0,0 +1,31 @@
successMessage: Container does not have any insecure capabilities
failureMessage: Container should not have insecure capabilities
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
securityContext:
type: object
properties:
capabilities:
type: object
properties:
add:
enum:
- CHOWN
- DAC_OVERRIDE
- FSETID
- FOWNER
- MKNOD
- NET_RAW
- SETGID
- SETUID
- SETFCAP
- SETPCAP
- NET_BIND_SERVICE
- SYS_CHROOT
- KILL
- AUDIT_WRITE
+21
View File
@@ -0,0 +1,21 @@
successMessage: Liveness probe is configured
failureMessage: Liveness probe should be configured
category: Health Checks
controllers:
exclude:
- Job
- CronJob
containers:
exclude:
- initContainer
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- livenessProbe
properties:
livenessProbe:
type: object
not:
const: null
+27
View File
@@ -0,0 +1,27 @@
successMessage: Memory limits are set
failureMessage: Memory limits should be set
category: Resources
target: Container
containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- limits
properties:
limits:
type: object
required:
- memory
properties:
memory:
type: string
not:
const: ''
+27
View File
@@ -0,0 +1,27 @@
successMessage: Memory requests are set
failureMessage: Memory requests should be set
category: Resources
target: Container
containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- requests
properties:
requests:
type: object
required:
- memory
properties:
memory:
type: string
not:
const: ''
+26
View File
@@ -0,0 +1,26 @@
successMessage: Multiple replicas are scheduled
failureMessage: Only one replica is scheduled
category: Reliability
target: Controller
controllers:
include:
- Deployment
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- Object
properties:
Object:
type: object
required:
- spec
properties:
spec:
type: object
required:
- replicas
properties:
replicas:
type: integer
minimum: 2
+16
View File
@@ -0,0 +1,16 @@
successMessage: Filesystem is read only
failureMessage: Filesystem should be read only
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- securityContext
properties:
securityContext:
required:
- readOnlyRootFilesystem
properties:
readOnlyRootFilesystem:
const: true
+9
View File
@@ -0,0 +1,9 @@
successMessage: Priority class has been set
failureMessage: Priority class should be set
category: Security
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- priorityClassName
+13
View File
@@ -0,0 +1,13 @@
successMessage: Privilege escalation not allowed
failureMessage: Privilege escalation should not be allowed
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
securityContext:
properties:
allowPrivilegeEscalation:
not:
const: true
+11
View File
@@ -0,0 +1,11 @@
successMessage: Image pull policy is "Always"
failureMessage: Image pull policy should be "Always"
category: Images
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
required:
- imagePullPolicy
properties:
imagePullPolicy:
const: Always
+21
View File
@@ -0,0 +1,21 @@
successMessage: Readiness probe is configured
failureMessage: Readiness probe should be configured
category: Health Checks
controllers:
exclude:
- Job
- CronJob
containers:
exclude:
- initContainer
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- readinessProbe
properties:
readinessProbe:
type: object
not:
const: null
+13
View File
@@ -0,0 +1,13 @@
successMessage: Not running as privileged
failureMessage: Should not be running as privileged
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
securityContext:
properties:
privileged:
not:
const: true
+52
View File
@@ -0,0 +1,52 @@
successMessage: Is not allowed to run as root
failureMessage: Should not be allowed to run as root
category: Security
target: Container
schemaTarget: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
goodSecurityContext:
type: object
anyOf:
- required:
- runAsUser
properties:
runAsUser:
minimum: 1
- required:
- runAsNonRoot
properties:
runAsNonRoot:
const: true
notBadSecurityContext:
type: object
properties:
runAsUser:
minimum: 1
runAsNonRoot:
const: true
type: object
anyOf:
# non-root specified at pod-level, and not overridden at container level
- required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
# non-root specified at container level
- properties:
containers:
type: array
items:
required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
+16
View File
@@ -0,0 +1,16 @@
successMessage: Image tag is specified
failureMessage: Image tag should be specified
category: Images
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
required:
- image
allOf:
- properties:
image:
pattern: ^.+:.+$
- properties:
image:
not:
pattern: ^.+:latest$
+151
View File
@@ -0,0 +1,151 @@
// Copyright 2020 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 cmd
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)
var setExitCode bool
var minScore int
var auditOutputURL string
var auditOutputFile string
var auditOutputFormat string
var resourceToAudit string
func init() {
rootCmd.AddCommand(auditCmd)
auditCmd.PersistentFlags().StringVar(&auditPath, "audit-path", "", "If specified, audits one or more YAML files instead of a cluster.")
auditCmd.PersistentFlags().BoolVar(&setExitCode, "set-exit-code-on-danger", false, "Set an exit code of 3 when the audit contains danger-level issues.")
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().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.")
}
var auditCmd = &cobra.Command{
Use: "audit",
Short: "Runs a one-time audit.",
Long: `Runs a one-time audit.`,
Run: func(cmd *cobra.Command, args []string) {
if displayName != "" {
config.DisplayName = displayName
}
auditData := runAndReportAudit(config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat)
summary := auditData.GetSummary()
score := summary.GetScore()
if setExitCode && summary.Dangers > 0 {
logrus.Infof("%d danger items found in audit", summary.Dangers)
os.Exit(3)
} else if minScore != 0 && score < uint(minScore) {
logrus.Infof("Audit score of %d is less than the provided minimum of %d", score, minScore)
os.Exit(4)
}
},
}
func runAndReportAudit(c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string) validator.AuditData {
// Create a kubernetes client resource provider
k, err := kube.CreateResourceProvider(auditPath, workload)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
os.Exit(1)
}
auditData, err := validator.RunAudit(c, k)
if err != nil {
logrus.Errorf("Error while running audit on resources: %v", err)
os.Exit(1)
}
var outputBytes []byte
if outputFormat == "score" {
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
} else if outputFormat == "yaml" {
jsonBytes, err := json.Marshal(auditData)
if err == nil {
outputBytes, err = yaml.JSONToYAML(jsonBytes)
}
} else {
outputBytes, err = json.MarshalIndent(auditData, "", " ")
}
if err != nil {
logrus.Errorf("Error marshalling audit: %v", err)
os.Exit(1)
}
if outputURL == "" && outputFile == "" {
os.Stdout.Write(outputBytes)
} else {
if outputURL != "" {
req, err := http.NewRequest("POST", outputURL, bytes.NewBuffer(outputBytes))
if err != nil {
logrus.Errorf("Error building request for output: %v", err)
os.Exit(1)
}
if outputFormat == "json" {
req.Header.Set("Content-Type", "application/json")
} else if outputFormat == "yaml" {
req.Header.Set("Content-Type", "application/x-yaml")
} else {
req.Header.Set("Content-Type", "text/plain")
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
logrus.Errorf("Error making request for output: %v", err)
os.Exit(1)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Errorf("Error reading response: %v", err)
os.Exit(1)
}
logrus.Infof("Received response: %v", body)
}
if outputFile != "" {
err := ioutil.WriteFile(outputFile, []byte(outputBytes), 0644)
if err != nil {
logrus.Errorf("Error writing output to file: %v", err)
os.Exit(1)
}
}
}
return auditData
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2020 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 cmd
import (
"fmt"
"net/http"
"github.com/fairwindsops/polaris/pkg/dashboard"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var serverPort int
var basePath string
var loadAuditFile string
func init() {
rootCmd.AddCommand(dashboardCmd)
dashboardCmd.PersistentFlags().IntVarP(&serverPort, "port", "p", 8080, "Port for the dashboard webserver.")
dashboardCmd.PersistentFlags().StringVar(&basePath, "base-path", "/", "Path on which the dashboard is served.")
dashboardCmd.PersistentFlags().StringVar(&loadAuditFile, "load-audit-file", "", "Runs the dashboard with data saved from a past audit.")
dashboardCmd.PersistentFlags().StringVar(&auditPath, "audit-path", "", "If specified, audits one or more YAML files instead of a cluster.")
dashboardCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
}
var dashboardCmd = &cobra.Command{
Use: "dashboard",
Short: "Runs the webserver for Polaris dashboard.",
Long: `Runs the webserver for Polaris dashboard.`,
Run: func(cmd *cobra.Command, args []string) {
if displayName != "" {
config.DisplayName = displayName
}
var auditDataPtr *validator.AuditData
if loadAuditFile != "" {
auditData := validator.ReadAuditFromFile(loadAuditFile)
auditDataPtr = &auditData
}
router := dashboard.GetRouter(config, auditPath, serverPort, basePath, auditDataPtr)
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
http.Handle("/", router)
logrus.Infof("Starting Polaris dashboard server on port %d", serverPort)
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil))
},
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2020 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 cmd
import (
"flag"
"os"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var configPath string
var disallowExemptions bool
var logLevel string
var auditPath string
var displayName string
var (
version string
)
func init() {
// Flags
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Location of Polaris configuration file.")
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any exemptions from configuration file.")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", logrus.InfoLevel.String(), "Logrus log level.")
flag.Parse()
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
}
var config conf.Configuration
var rootCmd = &cobra.Command{
Use: "polaris",
Short: "polaris",
Long: `Validation of best practices in your Kubernetes clusters.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
parsedLevel, err := logrus.ParseLevel(logLevel)
if err != nil {
logrus.Errorf("log-level flag has invalid value %s", logLevel)
} else {
logrus.SetLevel(parsedLevel)
}
config, err = conf.ParseFile(configPath)
if err != nil {
logrus.Errorf("Error parsing config at %s: %v", configPath, err)
os.Exit(1)
}
if disallowExemptions {
config.DisallowExemptions = true
}
},
Run: func(cmd *cobra.Command, args []string) {
logrus.Error("You must specify a sub-command.")
err := cmd.Help()
if err != nil {
logrus.Error(err)
}
os.Exit(1)
},
}
// Execute the stuff
func Execute(VERSION string) {
version = VERSION
if err := rootCmd.Execute(); err != nil {
logrus.Error(err)
os.Exit(1)
}
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2020 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 cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Prints the current version.",
Long: `Prints the current version of the tool.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Polaris version:" + version)
},
}
+157
View File
@@ -0,0 +1,157 @@
// Copyright 2020 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 cmd
import (
"io/ioutil"
"os"
"strings"
fwebhook "github.com/fairwindsops/polaris/pkg/webhook"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
apitypes "k8s.io/apimachinery/pkg/types"
k8sConfig "sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
var supportedVersions = map[string]runtime.Object{
"appsv1/Deployment": &appsv1.Deployment{},
"appsv1beta1/Deployment": &appsv1beta1.Deployment{},
"appsv1beta2/Deployment": &appsv1beta2.Deployment{},
"appsv1/StatefulSet": &appsv1.StatefulSet{},
"appsv1beta1/StatefulSet": &appsv1beta1.StatefulSet{},
"appsv1beta2/StatefulSet": &appsv1beta2.StatefulSet{},
"appsv1/DaemonSet": &appsv1.DaemonSet{},
"appsv1beta2/DaemonSet": &appsv1beta2.DaemonSet{},
"batchv1/Job": &batchv1.Job{},
"batchv1beta1/CronJob": &batchv1beta1.CronJob{},
"batchv2alpha1/CronJob": &batchv2alpha1.CronJob{},
"corev1/ReplicationController": &corev1.ReplicationController{},
"corev1/Pod": &corev1.Pod{},
}
var webhookPort int
var disableWebhookConfigInstaller bool
func init() {
rootCmd.AddCommand(webhookCmd)
webhookCmd.PersistentFlags().IntVarP(&webhookPort, "port", "p", 9876, "Port for the dashboard webserver.")
webhookCmd.PersistentFlags().BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false, "disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.")
}
var webhookCmd = &cobra.Command{
Use: "webhook",
Short: "Runs the webhook webserver.",
Long: `Runs the webhook webserver.`,
Run: func(cmd *cobra.Command, args []string) {
logrus.Debug("Setting up controller manager")
mgr, err := manager.New(k8sConfig.GetConfigOrDie(), manager.Options{})
if err != nil {
logrus.Errorf("Unable to set up overall controller manager: %v", err)
os.Exit(1)
}
polarisAppName := "polaris"
polarisResourceName := "polaris-webhook"
polarisNamespaceBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
// Not exiting here as we have fallback options
logrus.Debugf("Error reading namespace information: %v", err)
}
polarisNamespace := string(polarisNamespaceBytes)
if polarisNamespace == "" {
polarisNamespace = polarisResourceName
logrus.Debugf("Could not determine current namespace, creating resources in %s namespace", polarisNamespace)
}
logrus.Info("Setting up webhook server")
as, err := webhook.NewServer(polarisResourceName, mgr, webhook.ServerOptions{
Port: int32(webhookPort),
CertDir: "/opt/cert",
DisableWebhookConfigInstaller: &disableWebhookConfigInstaller,
BootstrapOptions: &webhook.BootstrapOptions{
ValidatingWebhookConfigName: polarisResourceName,
Secret: &apitypes.NamespacedName{
Namespace: polarisNamespace,
Name: polarisResourceName,
},
Service: &webhook.Service{
Namespace: polarisNamespace,
Name: polarisResourceName,
// Selectors should select the pods that runs this webhook server.
Selectors: map[string]string{
"app": polarisAppName,
"component": "webhook",
},
},
},
})
if err != nil {
logrus.Errorf("Error setting up webhook server: %v", err)
os.Exit(1)
}
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
// Iterate all the configurations supported controllers to scan and register them for webhooks
// Should only register controllers that are configured to be scanned
logrus.Debug("Registering webhooks to the webhook server")
var webhooks []webhook.Webhook
for name, supportedAPIType := range supportedVersions {
webhookName := strings.ToLower(name)
webhookName = strings.ReplaceAll(webhookName, "/", "-")
hook, err := fwebhook.NewWebhook(webhookName, mgr, fwebhook.Validator{Config: config}, supportedAPIType)
if err != nil {
logrus.Warningf("Couldn't build webhook %s: %v", webhookName, err)
continue
}
webhooks = append(webhooks, hook)
logrus.Infof("%s webhook started", webhookName)
}
if err = as.Register(webhooks...); err != nil {
logrus.Debugf("Unable to register webhooks in the admission server: %v", err)
os.Exit(1)
}
logrus.Debug("Starting webhook manager")
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
logrus.Errorf("Error starting manager: %v", err)
os.Exit(1)
}
},
}
+35 -111
View File
@@ -5,108 +5,63 @@ kind: Namespace
metadata:
name: polaris
---
# Source: polaris/templates/configmap.yaml
# Source: polaris/templates/rbac.yaml
apiVersion: v1
kind: ConfigMap
kind: ServiceAccount
metadata:
name: polaris
namespace: polaris
labels:
app: polaris
data:
config.yaml: |
resources:
cpuRequestsMissing: warning
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
images:
tagNotSpecified: error
healthChecks:
readinessProbeMissing: warning
livenessProbeMissing: warning
networking:
hostNetworkSet: warning
hostPortSet: warning
security:
hostIPCSet: error
hostPIDSet: error
notReadOnlyRootFileSystem: warning
privilegeEscalationAllowed: error
runAsRootAllowed: warning
runAsPrivileged: error
capabilities:
error:
ifAnyAdded:
- SYS_ADMIN
- NET_ADMIN
- ALL
warning:
ifAnyAddedBeyond:
- CHOWN
- DAC_OVERRIDE
- FSETID
- FOWNER
- MKNOD
- NET_RAW
- SETGID
- SETUID
- SETFCAP
- SETPCAP
- NET_BIND_SERVICE
- SYS_CHROOT
- KILL
- AUDIT_WRITE
---
# Source: polaris/templates/dashboard.rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: polaris-dashboard
namespace: polaris
labels:
app: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: polaris-dashboard
name: polaris
labels:
app: polaris
rules:
- apiGroups:
- 'apps'
- 'extensions'
resources:
- 'deployments'
- 'statefulsets'
verbs:
- 'get'
- 'list'
# required by controller-runtime code doing a cluster wide lookup
# when it seems namespace would suffice
- apiGroups:
- ''
resources:
- 'nodes'
- 'namespaces'
- 'pods'
verbs:
- 'get'
- 'list'
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: polaris-dashboard
name: polaris-view
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: polaris-dashboard
name: view
subjects:
- kind: ServiceAccount
name: polaris-dashboard
name: polaris
namespace: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: polaris
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: polaris
subjects:
- kind: ServiceAccount
name: polaris
namespace: polaris
---
# Source: polaris/templates/dashboard.service.yaml
@@ -117,9 +72,10 @@ metadata:
namespace: polaris
labels:
app: polaris
annotations:
spec:
ports:
- name: dashboard
- name: http-dashboard
port: 80
protocol: TCP
targetPort: 8080
@@ -132,8 +88,6 @@ spec:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
checksum/config: '6ec4a4dc87403cae67c01438398a5f1d4ef836ffeaf26a33b685c066b940495a'
name: polaris-dashboard
namespace: polaris
labels:
@@ -151,17 +105,11 @@ spec:
app: polaris
component: dashboard
spec:
volumes:
- name: config
configMap:
name: polaris
containers:
- command:
- polaris
- --dashboard
- --config
- /opt/app/config.yaml
image: 'quay.io/reactiveops/polaris:0.3'
- dashboard
image: 'quay.io/fairwinds/polaris:1.2'
imagePullPolicy: 'Always'
name: dashboard
ports:
@@ -180,8 +128,8 @@ spec:
periodSeconds: 20
resources:
limits:
cpu: 100m
memory: 128Mi
cpu: 150m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
@@ -193,30 +141,6 @@ spec:
capabilities:
drop:
- ALL
volumeMounts:
- name: config
mountPath: /opt/app/config.yaml
subPath: config.yaml
readOnly: true
serviceAccountName: polaris-dashboard
---
# Source: polaris/templates/audit.job.yaml
---
# Source: polaris/templates/audit.rbac.yaml
---
# Source: polaris/templates/ingress.yaml
---
# Source: polaris/templates/webhook.deployment.yaml
---
# Source: polaris/templates/webhook.rbac.yaml
---
# Source: polaris/templates/webhook.secret.yaml
---
# Source: polaris/templates/webhook.service.yaml
serviceAccountName: polaris
nodeSelector:
tolerations:
+64 -99
View File
@@ -5,7 +5,17 @@ kind: Namespace
metadata:
name: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: polaris
namespace: polaris
labels:
app: polaris
---
# Source: polaris/templates/webhook.secret.yaml
# The name of this secret is static as it is populated by the webhook pod.
apiVersion: v1
kind: Secret
metadata:
@@ -17,69 +27,25 @@ type: Opaque
stringData:
cert.pem: ''
---
# Source: polaris/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: polaris
namespace: polaris
labels:
app: polaris
data:
config.yaml: |
rules:
# required by controller-runtime code doing a cluster wide lookup
# when it seems namespace would suffice
- apiGroups:
- ''
resources:
cpuRequestsMissing: warning
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
images:
tagNotSpecified: error
healthChecks:
readinessProbeMissing: warning
livenessProbeMissing: warning
networking:
hostNetworkSet: warning
hostPortSet: warning
security:
hostIPCSet: error
hostPIDSet: error
notReadOnlyRootFileSystem: warning
privilegeEscalationAllowed: error
runAsRootAllowed: warning
runAsPrivileged: error
capabilities:
error:
ifAnyAdded:
- SYS_ADMIN
- NET_ADMIN
- ALL
warning:
ifAnyAddedBeyond:
- CHOWN
- DAC_OVERRIDE
- FSETID
- FOWNER
- MKNOD
- NET_RAW
- SETGID
- SETUID
- SETFCAP
- SETPCAP
- NET_BIND_SERVICE
- SYS_CHROOT
- KILL
- AUDIT_WRITE
- 'nodes'
verbs:
- 'get'
- 'list'
---
# Source: polaris/templates/webhook.rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: polaris-webhook
namespace: polaris
labels:
app: polaris
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
@@ -87,15 +53,6 @@ metadata:
labels:
app: polaris
rules:
- apiGroups:
- 'apps'
- 'extensions'
resources:
- 'deployments'
- 'statefulsets'
verbs:
- 'get'
- 'list'
# required by controller-runtime code doing a cluster wide lookup
# when it seems namespace would suffice
- apiGroups:
@@ -114,6 +71,39 @@ rules:
verbs:
- '*'
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: polaris-view
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: polaris
namespace: polaris
---
# Source: polaris/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: polaris
labels:
app: polaris
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: polaris
subjects:
- kind: ServiceAccount
name: polaris
namespace: polaris
---
# Source: polaris/templates/webhook.rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
@@ -126,9 +116,10 @@ roleRef:
name: polaris-webhook
subjects:
- kind: ServiceAccount
name: polaris-webhook
name: polaris
namespace: polaris
---
# Source: polaris/templates/webhook.rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
@@ -146,6 +137,7 @@ rules:
verbs:
- '*'
---
# Source: polaris/templates/webhook.rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
@@ -159,7 +151,7 @@ roleRef:
name: polaris-webhook
subjects:
- kind: ServiceAccount
name: polaris-webhook
name: polaris
namespace: polaris
---
# Source: polaris/templates/webhook.service.yaml
@@ -185,8 +177,6 @@ spec:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
checksum/config: '6ec4a4dc87403cae67c01438398a5f1d4ef836ffeaf26a33b685c066b940495a'
name: polaris-webhook
namespace: polaris
labels:
@@ -208,10 +198,8 @@ spec:
- name: webhook
command:
- polaris
- --webhook
- --config
- /opt/app/config.yaml
image: 'quay.io/reactiveops/polaris:0.3'
- webhook
image: 'quay.io/fairwinds/polaris:1.2'
imagePullPolicy: 'Always'
ports:
- containerPort: 9876
@@ -250,41 +238,18 @@ spec:
drop:
- ALL
volumeMounts:
- name: config
mountPath: /opt/app/config.yaml
subPath: config.yaml
readOnly: true
- name: secret
mountPath: /opt/cert/
readOnly: true
- name: cr-logs
mountPath: /tmp/
readOnly: false
serviceAccountName: polaris-webhook
serviceAccountName: polaris
nodeSelector:
tolerations:
volumes:
- name: config
configMap:
name: polaris
- name: secret
secret:
secretName: polaris-webhook
- name: cr-logs
emptyDir: {}
---
# Source: polaris/templates/audit.job.yaml
---
# Source: polaris/templates/audit.rbac.yaml
---
# Source: polaris/templates/dashboard.deployment.yaml
---
# Source: polaris/templates/dashboard.rbac.yaml
---
# Source: polaris/templates/dashboard.service.yaml
---
# Source: polaris/templates/ingress.yaml
@@ -4,14 +4,14 @@ Polaris supports a number of checks related to the image specified by pods.
key | default | description
----|---------|------------
`images.tagNotSpecified` | `error` | Fails when an image tag is either not specified or `latest`.
`images.tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
`images.pullPolicyNotAlways` | `ignore` | Fails when an image pull policy is not `always`.
## Background
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source. This is not a check enabled by default with Polaris as organizations may not wish to add the overhead involved with pulling images for each pod.
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
## Further Reading
+30
View File
@@ -0,0 +1,30 @@
# Resources
Polaris supports a number of checks related to CPU and Memory requests and limits.
## Presence Checks
To simplify ensure that these values have been set, the following attributes are available:
key | default | description
----|---------|------------
`resources.cpuRequestsMissing` | `danger` | Fails when `resources.requests.cpu` attribute is not configured.
`resources.memoryRequestsMissing` | `danger` | Fails when `resources.requests.memory` attribute is not configured.
`resources.cpuLimitsMissing` | `danger` | Fails when `resources.limits.cpu` attribute is not configured.
`resources.memoryLimitsMissing` | `danger` | Fails when `resources.limits.memory` attribute is not configured.
## Background
Configuring resource requests and limits for containers running in Kubernetes is an important best practice to follow. Setting appropriate resource requests will ensure that all your applications have sufficient compute resources. Setting appropriate resource limits will ensure that your applications do not consume too many resources.
Having these values appropriately configured ensures that:
* Cluster autoscaling can function as intended. New nodes are scheduled once pods are unable to be scheduled on an existing node due to insufficient resources. This will not happen if resource requests are not configured.
* Each container has sufficient access to compute resources. Without resource requests, a pod may be scheduled on a node that is already overutilized. Without resource limits, a single poorly behaving pod could utilize the majority of resources on a node, significantly impacting the performance of other pods on the same node.
## Further Reading
- [Kubernetes Docs: Managing Compute Resources for Containers](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/)
- [Kubernetes best practices: Resource requests and limits](https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-resource-requests-and-limits)
- [Vertical Pod Autoscaler (can automatically set resource requests and limits)](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler)
+27
View File
@@ -0,0 +1,27 @@
# Security
Polaris supports a number of checks related to security.
key | default | description
----|---------|------------
`security.hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
`security.hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
`security.notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
`security.privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
`security.runAsRootAllowed` | `danger` | Fails when `securityContext.runAsNonRoot` is not true.
`security.runAsPrivileged` | `danger` | Fails when `securityContext.privileged` is true.
`security.insecureCapabilities` | `warning` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](/checks/insecureCapabilities.yaml)
`security.dangerousCapabilities` | `danger` | Fails when `securityContext.capabilities` includes one of the capabilities [listed here](/checks/dangerousCapabilities.yaml)
## Background
Securing workloads in Kubernetes is an important part of overall cluster security. The overall goal should be to ensure that containers are running with as minimal privileges as possible. This includes avoiding privilege escalation, not running containers with a root user, and using read only file systems wherever possible.
Much of this configuration can be found in the `securityContext` attribute for both Kubernetes pods and containers. Where configuration is available at both a pod and container level, Polaris validates both.
## Further Reading
- [Kubernetes Docs: Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
- [KubeCon 2018 Keynote: Running with Scissors](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
- [Kubernetes Security Book](https://kubernetes-security.info/)
- [Kubernetes Docs: Set capabilities for a Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container)
- [Linux Programmer's Manual: Capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html)
+13
View File
@@ -0,0 +1,13 @@
# Polaris Exit Codes for Audit Runs
<dl>
<dt>Exit 0</dt>
<dd>Successful exit code</dd>
<dt>Exit 1</dt>
<dd>Could not run audit, or application had a failure while running.</dd>
<dt>Exit 2</dt>
<dd>Unused</dd>
<dt>Exit 3</dt>
<dd>Exiting due to `--set-exit-code-on-danger` being set and at least one danger was found after an audit.</dd>
<dt>Edit 4</dd>
<dd>Exiting due to `--set-exit-code-below-score` being set and the audit resulted in a score less than the minimum score value.</dd>
</dl>
-41
View File
@@ -1,41 +0,0 @@
# Resources
Polaris supports a number of checks related to CPU and Memory requests and limits.
## Presence Checks
To simplify ensure that these values have been set, the following attributes are available:
key | default | description
----|---------|------------
`resources.cpuRequestsMissing` | `error` | Fails when `resources.requests.cpu` attribute is not configured.
`resources.memoryRequestsMissing` | `error` | Fails when `resources.requests.memory` attribute is not configured.
`resources.cpuLimitsMissing` | `error` | Fails when `resources.limits.cpu` attribute is not configured.
`resources.memoryLimitsMissing` | `error` | Fails when `resources.limits.memory` attribute is not configured.
## Range Checks
Polaris can also verify that those values fall within a certain range. These checks are not enabled by default, and as such do not have default values. The `cpuRequestRanges`, `cpuLimitRanges`, `memoryRequestRanges`, and `memoryLimitRanges` all support the following attributes:
key | description
----|------------
`warning.below` | Warn when resource is below this value (or not defined)
`warning.above` | Warn when resource is above this value
`error.below` | Error when resource is below this value (or not defined)
`error.above` | Error when resource is above this value
## Background
Configuring resource requests and limits for containers running in Kubernetes is an important best practice to follow. Setting appropriate resource requests will ensure that all your applications have sufficient compute resources. Setting appropriate resource limits will ensure that your applications do not consume too many resources.
Having these values appropriately configured ensures that:
* Cluster autoscaling can function as intended. New nodes are scheduled once pods are unable to be scheduled on an existing node due to insufficient resources. This will not happen if resource requests are not configured.
* Each container has sufficient access to compute resources. Without resource requests, a pod may be scheduled on a node that is already overutilized. Without resource limits, a single poorly behaving pod could utilize the majority of resources on a node, significantly impacting the performance of other pods on the same node.
## Further Reading
- [Kubernetes Docs: Managing Compute Resources for Containers](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/)
- [Kubernetes best practices: Resource requests and limits](https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-resource-requests-and-limits)
- [Vertical Pod Autoscaler (can automatically set resource requests and limits)](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler)
-23
View File
@@ -1,23 +0,0 @@
# Security Capabilities
Polaris supports a number of checks to ensure pods are running with a limited set of capabilities. Under `security.capabilities`, there are `error` and `warning` sections indicating the severity of failures for the following checks.
key | default | description
----|---------|------------
`security.capabilities.error.ifAnyAdded` | [`SYS_ADMIN`, `NET_ADMIN`, `ALL`] | Fails when any of the listed capabilities have been added.
`security.capabilities.error.ifAnyAddedBeyond` | `nil` | Fails when any capabilities have been added beyond the specified list.
`security.capabilities.error.ifAnyNotDropped` | `nil` | Fails when any of the listed capabilities have not been dropped.
`security.capabilities.warning.ifAnyAdded` | `nil` | Fails when any of the listed capabilities have been added.
`security.capabilities.warning.ifAnyAddedBeyond` | [`CHOWN`, `DAC_OVERRIDE`, `FSETID`, `FOWNER`, `MKNOD`, `NET_RAW`, `SETGID`, `SETUID`, `SETFCAP`, `SETPCAP`, `NET_BIND_SERVICE`, `SYS_CHROOT`, `KILL`,`AUDIT_WRITE`] | Fails when any capabilities have been added beyond the specified list.
`security.capabilities.warning.ifAnyNotDropped` | `nil` | Fails when any of the listed capabilities have not been dropped.
## Background
Linux Capabilities allow you to specify privileges for a process at a granular level. The [default list of capabilities](https://github.com/moby/moby/blob/master/oci/defaults.go#L15) included with a container are already fairly minimal, but often can be further restricted.
With Kubernetes configuration, these capabilities can be added or removed by adjusting `securityContext.capabilities`.
## Further Reading
- [Kubernetes Docs: Set capabilities for a Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container)
- [Linux Programmer's Manual: Capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html)
-27
View File
@@ -1,27 +0,0 @@
# Security
Polaris supports a number of checks related to security.
key | default | description
----|---------|------------
`security.hostIPCSet` | `error` | Fails when `hostIPC` attribute is configured.
`security.hostPIDSet` | `error` | Fails when `hostPID` attribute is configured.
`security.notReadOnlyRootFileSystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
`security.privilegeEscalationAllowed` | `error` | Fails when `securityContext.allowPrivilegeEscalation` is true.
`security.runAsRootAllowed` | `error` | Fails when `securityContext.runAsNonRoot` is not true.
`security.runAsPrivileged` | `error` | Fails when `securityContext.privileged` is true.
## Security Capabilities
Additional validations are available to ensure pods are running with a limited set of capabilities. More information is available in our [Security Capabilities documentation](security-capabilities.md).
## Background
Securing workloads in Kubernetes is an important part of overall cluster security. The overall goal should be to ensure that containers are running with as minimal privileges as possible. This includes avoiding privilege escalation, not running containers with a root user, and using read only file systems wherever possible.
Much of this configuration can be found in the `securityContext` attribute for both Kubernetes pods and containers. Where configuration is available at both a pod and container level, Polaris validates both.
## Further Reading
- [Kubernetes Docs: Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
- [KubeCon 2018 Keynote: Running with Scissors](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
- [Kubernetes Security Book](https://kubernetes-security.info/)
+241
View File
@@ -0,0 +1,241 @@
# Installation and Usage
Polaris can be installed on your cluster using kubectl or Helm. It can also
be run as a local binary, which will use your kubeconfig to connect to the cluster
or run against local YAML files.
## Configuration
Polaris supports a wide range of validations covering a number of Kubernetes best practices.
Here's a [sample configuration file](/examples/config-full.yaml) that includes all currently supported checks.
The [default configuration](/examples/config.yaml) contains a number of those checks.
### Checks
Each check can be assigned a `severity`. Only checks with a severity of `danger` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `danger` will result in a change being rejected.
Polaris validation checks fall into several different categories:
- [Health Checks](check-documentation/health-checks.md)
- [Images](check-documentation/images.md)
- [Networking](check-documentation/networking.md)
- [Resources](check-documentation/resources.md)
- [Security](check-documentation/security.md)
#### Custom Checks
If you'd like to create your own checks, you can use [JSON Schema](https://json-schema.org/). For example,
to disallow images from quay.io:
```yaml
checks:
imageRegistry: warning
customChecks:
imageRegistry:
successMessage: Image comes from allowed registries
failureMessage: Image should not be from disallowed registry
category: Images
target: Container # target can be "Container" or "Pod"
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
image:
type: string
not:
pattern: ^quay.io
```
Schemas can also be specified as JSON strings instead of YAML, for easier copy/pasting:
```yaml
customChecks:
foo:
jsonSchema: |
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object"
}
```
We extend JSON Schema with `resourceMinimum` and `resourceMaximum` fields to help compare memory and CPU resource
strings like `1000m` and `1G`. You can see an example in [the extended config](/examples/config-full.yaml)
There are additional examples in the [checks folder](/checks).
### Exemptions
Sometimes a workload really does need to do things that Polaris considers insecure. For instance,
many of the `kube-system` workloads need to run as root, or need access to the host network. In these
cases, we can add **exemptions** to allow the workload to pass Polaris checks.
Exemptions can be added two ways: by annotating a controller, or editing the Polaris config.
To exempt a controller from all checks via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g.
```
kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true
```
To exempt a controller via the config, you have to specify a list of controller names and a list of rules, e.g.
```yaml
exemptions:
- controllerNames:
- dns-controller
rules:
- hostNetworkSet
```
To exempt a controller from a particular check via annotations, use an annotation in the form of `polaris.fairwinds.com/<check>-exempt=true`, e.g.
```
kubectl annotate deployment my-deployment polaris.fairwinds.com/cpuRequestsMissing-exempt=true
```
# Installing
There are several ways to install and use Polaris. Below outline ways to install using `kubectl`, `helm` and `local binary`.
## Dashboard
The dashboard can be installed on a cluster using kubectl or Helm. It can also be run locally,
connecting to your cluster using the credentials stored in your `KUBECONFIG`.
### kubectl
```bash
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/dashboard.yaml
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
### Helm
```bash
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm upgrade --install polaris fairwinds-stable/polaris --namespace polaris
kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80
```
### Local Binary
You'll need a valid `KUBECONFIG` set up for the dashboard to connect to your cluster.
Binary releases can be dowloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
or can be installed with [Homebrew](https://brew.sh/):
```bash
brew tap reactiveops/tap
brew install reactiveops/tap/polaris
polaris dashboard --port 8080
```
You can also point the dashboard to the local filesystem, instead of a live cluster:
```bash
polaris dashboard --port 8080 --audit-path=./deploy/
```
## Webhook
### kubectl
```bash
kubectl apply -f https://github.com/fairwindsops/polaris/releases/latest/download/webhook.yaml
```
### Helm
```bash
helm repo add fairwindsops-stable https://charts.fairwindsops.com/stable
helm upgrade --install polaris fairwindsops-stable/polaris --namespace polaris \
--set webhook.enable=true --set dashboard.enable=false
```
## CLI
### Installation
Binary releases can be downloaded from the [releases page](https://github.com/fairwindsops/polaris/releases)
or can be installed with [Homebrew](https://brew.sh/):
```bash
brew tap FairwindsOps/tap
brew install FairwindsOps/tap/polaris
polaris version
```
You can run audits on the command line and see the output as JSON, YAML, or a raw score:
```bash
polaris audit --format yaml > report.yaml
polaris audit --format score
# 92
```
Audits can run against a local directory or YAML file rather than a cluster:
```bash
polaris audit --audit-path ./deploy/
# or to use STDIN
cat pod.yaml | polaris audit --audit-path -
```
You can also run the audit on a single resource instead of the entire cluster:
```bash
polaris audit --resource "nginx-ingress/Deployment.apps/v1/default-backend"
```
#### Running with CI/CD
You can integrate Polaris into CI/CD for repositories containing infrastructure-as-code.
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
```bash
polaris audit --audit-path ./deploy/ \
--set-exit-code-on-danger \
--set-exit-code-below-score 90
```
For more on exit code meanings, see [exit-code docs](exit-codes.md).
#### 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)
```
+78 -57
View File
@@ -1,65 +1,86 @@
resources:
checks:
# reliability
multipleReplicasForDeployment: warning
priorityClassNotSet: warning
# resources
cpuRequestsMissing: warning
cpuRequestRanges:
warning:
below: 50m
above: 1000m
error:
below: 500m
above: 2000m
cpuLimitsMissing: warning
cpuLimitRanges:
warning:
below: 50m
above: 1000m
error:
below: 500m
above: 2000m
memoryRequestsMissing: warning
memoryRequestRanges:
warning:
below: 50M
above: 2G
error:
below: 100M
above: 4G
memoryLimitsMissing: warning
memoryLimitRanges:
warning:
below: 50M
above: 2G
error:
below: 100M
above: 4G
images:
tagNotSpecified: error
# images
tagNotSpecified: danger
pullPolicyNotAlways: warning
whitelist:
error:
- gcr.io/*
blacklist:
warning:
- docker.io/*
healthChecks:
# healthChecks
readinessProbeMissing: warning
livenessProbeMissing: warning
networking:
hostNetworkSet: error
hostPortSet: error
security:
hostIPCSet: error
hostPIDSet: error
# networking
hostNetworkSet: warning
hostPortSet: warning
# security
hostIPCSet: danger
hostPIDSet: danger
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: warning
runAsPrivileged: error
notReadOnlyRootFileSystem: warning
privilegeEscalationAllowed: error
capabilities:
error:
ifAnyAdded:
- SYS_ADMIN
- ALL
ifAnyNotDropped:
- ALL
warning:
ifAnyAddedBeyond:
- NONE
runAsPrivileged: danger
dangerousCapabilities: danger
insecureCapabilities: warning
# custom
resourceLimits: warning
imageRegistry: danger
exemptions:
- controllerNames:
- my-network-controller
rules:
- hostNetworkSet
- hostPortSet
customChecks:
resourceLimits:
containers:
exclude:
- initContainer
successMessage: Resource limits are within the required range
failureMessage: Resource limits should be within the required range
category: Resources
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- resources
properties:
resources:
type: object
required:
- limits
properties:
limits:
type: object
required:
- memory
- cpu
properties:
memory:
type: string
resourceMinimum: 100M
resourceMaximum: 6G
cpu:
type: string
resourceMinimum: 100m
resourceMaximum: "2"
imageRegistry:
successMessage: Image comes from allowed registries
failureMessage: Image should not be from disallowed registry
category: Images
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
image:
type: string
not:
pattern: ^quay.io
+191 -34
View File
@@ -1,43 +1,200 @@
resources:
checks:
# reliability
multipleReplicasForDeployment: ignore
priorityClassNotSet: ignore
# resources
cpuRequestsMissing: warning
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
images:
tagNotSpecified: error
pullPolicyNotAlways: ignore
healthChecks:
# images
tagNotSpecified: danger
pullPolicyNotAlways: warning
# healthChecks
readinessProbeMissing: warning
livenessProbeMissing: warning
networking:
# networking
hostNetworkSet: warning
hostPortSet: warning
security:
hostIPCSet: error
hostPIDSet: error
notReadOnlyRootFileSystem: warning
privilegeEscalationAllowed: error
# security
hostIPCSet: danger
hostPIDSet: danger
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: warning
runAsPrivileged: error
capabilities:
error:
ifAnyAdded:
- SYS_ADMIN
- NET_ADMIN
- ALL
warning:
ifAnyAddedBeyond:
- CHOWN
- DAC_OVERRIDE
- FSETID
- FOWNER
- MKNOD
- NET_RAW
- SETGID
- SETUID
- SETFCAP
- SETPCAP
- NET_BIND_SERVICE
- SYS_CHROOT
- KILL
- AUDIT_WRITE
runAsPrivileged: danger
dangerousCapabilities: danger
insecureCapabilities: warning
exemptions:
- controllerNames:
- kube-apiserver
- kube-proxy
- kube-scheduler
- etcd-manager-events
- kube-controller-manager
- kube-dns
- etcd-manager-main
rules:
- hostPortSet
- hostNetworkSet
- readinessProbeMissing
- livenessProbeMissing
- cpuRequestsMissing
- cpuLimitsMissing
- memoryRequestsMissing
- memoryLimitsMissing
- runAsRootAllowed
- runAsPrivileged
- notReadOnlyRootFilesystem
- hostPIDSet
- controllerNames:
- kube-flannel-ds
rules:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- notReadOnlyRootFilesystem
- readinessProbeMissing
- livenessProbeMissing
- cpuLimitsMissing
- controllerNames:
- cert-manager
rules:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- controllerNames:
- cluster-autoscaler
rules:
- notReadOnlyRootFilesystem
- runAsRootAllowed
- readinessProbeMissing
- controllerNames:
- vpa
rules:
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- notReadOnlyRootFilesystem
- controllerNames:
- datadog
rules:
- runAsRootAllowed
- readinessProbeMissing
- livenessProbeMissing
- notReadOnlyRootFilesystem
- controllerNames:
- nginx-ingress-controller
rules:
- privilegeEscalationAllowed
- insecureCapabilities
- runAsRootAllowed
- controllerNames:
- dns-controller
- datadog-datadog
- kube-flannel-ds
- kube2iam
- aws-iam-authenticator
- datadog
- kube2iam
rules:
- hostNetworkSet
- controllerNames:
- aws-iam-authenticator
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- dnsmasq
- autoscaler
- kubernetes-dashboard
- install-cni
- kube2iam
rules:
- readinessProbeMissing
- livenessProbeMissing
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- runAsRootAllowed
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-controller
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- notReadOnlyRootFilesystem
- controllerNames:
- cert-manager
- dns-controller
- kubedns
- dnsmasq
- autoscaler
- insights-agent-goldilocks-vpa-install
- datadog
rules:
- cpuRequestsMissing
- cpuLimitsMissing
- memoryRequestsMissing
- memoryLimitsMissing
- controllerNames:
- kube2iam
- kube-flannel-ds
rules:
- runAsPrivileged
- controllerNames:
- kube-hunter
rules:
- hostPIDSet
- controllerNames:
- polaris
- kube-hunter
- goldilocks
- insights-agent-goldilocks-vpa-install
rules:
- notReadOnlyRootFilesystem
- controllerNames:
- insights-agent-goldilocks-controller
rules:
- livenessProbeMissing
- readinessProbeMissing
- controllerNames:
- insights-agent-goldilocks-vpa-install
- kube-hunter
rules:
- runAsRootAllowed
-29
View File
@@ -1,29 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: polaris-failing-deployment
labels:
app: polaris-failing-deployment
spec:
replicas: 1
selector:
matchLabels:
app: polaris-failing-deployment
template:
metadata:
labels:
app: polaris-failing-deployment
spec:
containers:
- name: this-will-fail
image: 'alpine'
command: [ "/bin/sh", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
securityContext:
privileged: true
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsNonRoot: false
capabilities:
add:
- ALL
+281
View File
@@ -0,0 +1,281 @@
{
"PolarisOutputVersion": "1.0",
"AuditTime": "0001-01-01T00:00:00Z",
"SourceType": "Path",
"SourceName": "deploy/",
"DisplayName": "deploy/",
"ClusterInfo": {
"Version": "unknown",
"Nodes": 0,
"Pods": 0,
"Namespaces": 2,
"Deployments": 2,
"StatefulSets": 0,
"DaemonSets": 0,
"Jobs": 0,
"CronJobs": 0,
"ReplicationControllers": 0
},
"Results": [
{
"Name": "polaris-dashboard",
"Namespace": "polaris",
"Kind": "Deployment",
"Messages": {},
"PodResult": {
"Name": "",
"Messages": {
"hostIPCSet": {
"ID": "hostIPCSet",
"Message": "Host IPC is not configured",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"hostNetworkSet": {
"ID": "hostNetworkSet",
"Message": "Host network is not configured",
"Success": true,
"Severity": "warning",
"Category": "Networking"
},
"hostPIDSet": {
"ID": "hostPIDSet",
"Message": "Host PID is not configured",
"Success": true,
"Severity": "danger",
"Category": "Security"
}
},
"ContainerResults": [
{
"Name": "dashboard",
"Messages": {
"cpuLimitsMissing": {
"ID": "cpuLimitsMissing",
"Message": "CPU limits are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"cpuRequestsMissing": {
"ID": "cpuRequestsMissing",
"Message": "CPU requests are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"dangerousCapabilities": {
"ID": "dangerousCapabilities",
"Message": "Container does not have any dangerous capabilities",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"hostPortSet": {
"ID": "hostPortSet",
"Message": "Host port is not configured",
"Success": true,
"Severity": "warning",
"Category": "Networking"
},
"insecureCapabilities": {
"ID": "insecureCapabilities",
"Message": "Container does not have any insecure capabilities",
"Success": true,
"Severity": "warning",
"Category": "Security"
},
"livenessProbeMissing": {
"ID": "livenessProbeMissing",
"Message": "Liveness probe is configured",
"Success": true,
"Severity": "warning",
"Category": "Health Checks"
},
"memoryLimitsMissing": {
"ID": "memoryLimitsMissing",
"Message": "Memory limits are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"memoryRequestsMissing": {
"ID": "memoryRequestsMissing",
"Message": "Memory requests are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"privilegeEscalationAllowed": {
"ID": "privilegeEscalationAllowed",
"Message": "Privilege escalation not allowed",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"readinessProbeMissing": {
"ID": "readinessProbeMissing",
"Message": "Readiness probe is configured",
"Success": true,
"Severity": "warning",
"Category": "Health Checks"
},
"runAsPrivileged": {
"ID": "runAsPrivileged",
"Message": "Not running as privileged",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"runAsRootAllowed": {
"ID": "runAsRootAllowed",
"Message": "Is not allowed to run as root",
"Success": true,
"Severity": "warning",
"Category": "Security"
},
"tagNotSpecified": {
"ID": "tagNotSpecified",
"Message": "Image tag is specified",
"Success": true,
"Severity": "danger",
"Category": "Images"
}
}
}
]
}
},
{
"Name": "polaris-webhook",
"Namespace": "polaris",
"Kind": "Deployment",
"Messages": {},
"PodResult": {
"Name": "",
"Messages": {
"hostIPCSet": {
"ID": "hostIPCSet",
"Message": "Host IPC is not configured",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"hostNetworkSet": {
"ID": "hostNetworkSet",
"Message": "Host network is not configured",
"Success": true,
"Severity": "warning",
"Category": "Networking"
},
"hostPIDSet": {
"ID": "hostPIDSet",
"Message": "Host PID is not configured",
"Success": true,
"Severity": "danger",
"Category": "Security"
}
},
"ContainerResults": [
{
"Name": "webhook",
"Messages": {
"cpuLimitsMissing": {
"ID": "cpuLimitsMissing",
"Message": "CPU limits are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"cpuRequestsMissing": {
"ID": "cpuRequestsMissing",
"Message": "CPU requests are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"dangerousCapabilities": {
"ID": "dangerousCapabilities",
"Message": "Container does not have any dangerous capabilities",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"hostPortSet": {
"ID": "hostPortSet",
"Message": "Host port is not configured",
"Success": true,
"Severity": "warning",
"Category": "Networking"
},
"insecureCapabilities": {
"ID": "insecureCapabilities",
"Message": "Container does not have any insecure capabilities",
"Success": true,
"Severity": "warning",
"Category": "Security"
},
"livenessProbeMissing": {
"ID": "livenessProbeMissing",
"Message": "Liveness probe is configured",
"Success": true,
"Severity": "warning",
"Category": "Health Checks"
},
"memoryLimitsMissing": {
"ID": "memoryLimitsMissing",
"Message": "Memory limits are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"memoryRequestsMissing": {
"ID": "memoryRequestsMissing",
"Message": "Memory requests are set",
"Success": true,
"Severity": "warning",
"Category": "Resources"
},
"privilegeEscalationAllowed": {
"ID": "privilegeEscalationAllowed",
"Message": "Privilege escalation not allowed",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"readinessProbeMissing": {
"ID": "readinessProbeMissing",
"Message": "Readiness probe is configured",
"Success": true,
"Severity": "warning",
"Category": "Health Checks"
},
"runAsPrivileged": {
"ID": "runAsPrivileged",
"Message": "Not running as privileged",
"Success": true,
"Severity": "danger",
"Category": "Security"
},
"runAsRootAllowed": {
"ID": "runAsRootAllowed",
"Message": "Is not allowed to run as root",
"Success": true,
"Severity": "warning",
"Category": "Security"
},
"tagNotSpecified": {
"ID": "tagNotSpecified",
"Message": "Image tag is specified",
"Success": true,
"Severity": "danger",
"Category": "Images"
}
}
}
]
}
}
]
}
-52
View File
@@ -1,52 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: polaris-passing-deployment
labels:
app: polaris-passing-deployment
spec:
replicas: 1
selector:
matchLabels:
app: polaris-passing-deployment
template:
metadata:
labels:
app: polaris-passing-deployment
spec:
containers:
- command:
- polaris
- --dashboard
image: 'quay.io/reactiveops/polaris:0.1.0'
imagePullPolicy: 'Always'
name: dashboard
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 20
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 20
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
+97
View File
@@ -0,0 +1,97 @@
module github.com/fairwindsops/polaris
go 1.13
require (
cloud.google.com/go v0.61.0
contrib.go.opencensus.io/exporter/ocagent v0.7.0
git.apache.org/thrift.git v0.12.0 // indirect
github.com/Azure/go-autorest v12.4.3+incompatible
github.com/Azure/go-autorest/autorest v0.10.0 // indirect
github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30
github.com/beorn7/perks v1.0.1
github.com/census-instrumentation/opencensus-proto v0.3.0
github.com/davecgh/go-spew v1.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/go-logr/logr v0.1.0
github.com/go-logr/zapr v0.1.1
github.com/gobuffalo/depgen v0.1.0 // indirect
github.com/gobuffalo/envy v1.9.0
github.com/gobuffalo/genny v0.6.0
github.com/gobuffalo/gogen v0.2.0
github.com/gobuffalo/logger v1.0.3
github.com/gobuffalo/mapi v1.2.1
github.com/gobuffalo/packd v1.0.0
github.com/gobuffalo/packr/v2 v2.8.0
github.com/gobuffalo/syncx v0.1.0
github.com/gogo/protobuf v1.3.1
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 // indirect
github.com/golang/protobuf v1.4.2
github.com/google/btree v1.0.0
github.com/google/gofuzz v1.1.0
github.com/google/uuid v1.1.1
github.com/googleapis/gnostic v0.3.1
github.com/gophercloud/gophercloud v0.0.0-20190516165734-b3a23cc94cc5
github.com/gorilla/mux v1.7.4
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc
github.com/grpc-ecosystem/grpc-gateway v1.14.6
github.com/hashicorp/golang-lru v0.5.4
github.com/imdario/mergo v0.3.10
github.com/joho/godotenv v1.3.0
github.com/json-iterator/go v1.1.10
github.com/karrick/godirwalk v1.15.6
github.com/konsorten/go-windows-terminal-sequences v1.0.3
github.com/markbates/oncer v1.0.0
github.com/markbates/safe v1.0.1
github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/modern-go/reflect2 v1.0.1
github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709
github.com/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.10.0
github.com/prometheus/procfs v0.1.3
github.com/qri-io/jsonschema v0.1.1
github.com/rogpeppe/go-internal v1.6.0
github.com/sirupsen/logrus v1.6.0
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a
gitlab.com/golang-commonmark/linkify v0.0.0-20180917065525-c22b7bdb1179
gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f
gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2
gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe
go.opencensus.io v0.22.4
go.uber.org/atomic v1.6.0
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.15.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1
golang.org/x/text v0.3.3
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed
google.golang.org/api v0.29.0
google.golang.org/appengine v1.6.6
google.golang.org/genproto v0.0.0-20200711021454-869866162049
google.golang.org/grpc v1.30.0
gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
k8s.io/api v0.0.0-20181213150558-05914d821849
k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93
k8s.io/client-go v0.0.0-20181213151034-8d9ed539ba31
k8s.io/klog v0.4.0
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22
sigs.k8s.io/controller-runtime v0.1.10
sigs.k8s.io/yaml v1.2.0
)
+1008
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+4 -242
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -15,252 +15,14 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/pkg/dashboard"
"github.com/reactiveops/polaris/pkg/kube"
"github.com/reactiveops/polaris/pkg/validator"
fwebhook "github.com/reactiveops/polaris/pkg/webhook"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apitypes "k8s.io/apimachinery/pkg/types"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Required for other auth providers like GKE.
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/yaml"
"github.com/fairwindsops/polaris/cmd/polaris"
)
const (
// Version represents the current release version of Polaris
Version = "0.3.1"
Version = "1.2.1"
)
func main() {
dashboard := flag.Bool("dashboard", false, "Runs the webserver for Polaris dashboard.")
webhook := flag.Bool("webhook", false, "Runs the webhook webserver.")
audit := flag.Bool("audit", false, "Runs a one-time audit.")
auditPath := flag.String("audit-path", "", "If specified, audits one or more YAML files instead of a cluster")
setExitCode := flag.Bool("set-exit-code-on-error", false, "When running with --audit, set an exit code of 3 when the audit contains error-level issues.")
minScore := flag.Int("set-exit-code-below-score", 0, "When running with --audit, set an exit code of 4 when the score is below this threshold (1-100)")
dashboardPort := flag.Int("dashboard-port", 8080, "Port for the dashboard webserver")
dashboardBasePath := flag.String("dashboard-base-path", "/", "Path on which the dashboard is served")
webhookPort := flag.Int("webhook-port", 9876, "Port for the webhook webserver")
auditOutputURL := flag.String("output-url", "", "Destination URL to send audit results")
auditOutputFile := flag.String("output-file", "", "Destination file for audit results")
auditOutputFormat := flag.String("output-format", "json", "Output format for results - json, yaml, or score")
displayName := flag.String("display-name", "", "An optional identifier for the audit")
configPath := flag.String("config", "", "Location of Polaris configuration file")
logLevel := flag.String("log-level", logrus.InfoLevel.String(), "Logrus log level")
version := flag.Bool("version", false, "Prints the version of Polaris")
disableWebhookConfigInstaller := flag.Bool("disable-webhook-config-installer", false,
"disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping")
flag.Parse()
if *version {
fmt.Printf("Polaris version %s\n", Version)
os.Exit(0)
}
parsedLevel, err := logrus.ParseLevel(*logLevel)
if err != nil {
logrus.Errorf("log-level flag has invalid value %s", *logLevel)
} else {
logrus.SetLevel(parsedLevel)
}
c, err := conf.ParseFile(*configPath)
if *displayName != "" {
c.DisplayName = *displayName
}
if err != nil {
logrus.Errorf("Error parsing config at %s: %v", *configPath, err)
os.Exit(1)
}
if !*dashboard && !*webhook && !*audit {
*audit = true
}
if *webhook {
startWebhookServer(c, *disableWebhookConfigInstaller, *webhookPort)
} else if *dashboard {
startDashboardServer(c, *auditPath, *dashboardPort, *dashboardBasePath)
} else if *audit {
auditData := runAndReportAudit(c, *auditPath, *auditOutputFile, *auditOutputURL, *auditOutputFormat)
if *setExitCode && auditData.ClusterSummary.Results.Totals.Errors > 0 {
logrus.Infof("%d errors found in audit", auditData.ClusterSummary.Results.Totals.Errors)
os.Exit(3)
} else if *minScore != 0 && auditData.ClusterSummary.Score < uint(*minScore) {
logrus.Infof("Audit score of %d is less than the provided minimum of %d", auditData.ClusterSummary.Score, *minScore)
os.Exit(4)
}
}
}
func startDashboardServer(c conf.Configuration, auditPath string, port int, basePath string) {
router := dashboard.GetRouter(c, auditPath, port, basePath)
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
http.Handle("/", router)
logrus.Infof("Starting Polaris dashboard server on port %d", port)
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
func startWebhookServer(c conf.Configuration, disableWebhookConfigInstaller bool, port int) {
logrus.Debug("Setting up controller manager")
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{})
if err != nil {
logrus.Errorf("Unable to set up overall controller manager: %v", err)
os.Exit(1)
}
polarisAppName := "polaris"
polarisResourceName := "polaris-webhook"
polarisNamespaceBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
// Not exiting here as we have fallback options
logrus.Debugf("Error reading namespace information: %v", err)
}
polarisNamespace := string(polarisNamespaceBytes)
if polarisNamespace == "" {
polarisNamespace = polarisResourceName
logrus.Debugf("Could not determine current namespace, creating resources in %s namespace", polarisNamespace)
}
logrus.Info("Setting up webhook server")
as, err := webhook.NewServer(polarisResourceName, mgr, webhook.ServerOptions{
Port: int32(port),
CertDir: "/opt/cert",
DisableWebhookConfigInstaller: &disableWebhookConfigInstaller,
BootstrapOptions: &webhook.BootstrapOptions{
ValidatingWebhookConfigName: polarisResourceName,
Secret: &apitypes.NamespacedName{
Namespace: polarisNamespace,
Name: polarisResourceName,
},
Service: &webhook.Service{
Namespace: polarisNamespace,
Name: polarisResourceName,
// Selectors should select the pods that runs this webhook server.
Selectors: map[string]string{
"app": polarisAppName,
"component": "webhook",
},
},
},
})
if err != nil {
logrus.Errorf("Error setting up webhook server: %v", err)
os.Exit(1)
}
logrus.Infof("Polaris webhook server listening on port %d", port)
d1 := fwebhook.NewWebhook("deployments", mgr, fwebhook.Validator{Config: c}, &appsv1.Deployment{})
d2 := fwebhook.NewWebhook("deployments-ext", mgr, fwebhook.Validator{Config: c}, &extensionsv1beta1.Deployment{})
logrus.Debug("Registering webhooks to the webhook server")
if err = as.Register(d1, d2); err != nil {
logrus.Debugf("Unable to register webhooks in the admission server: %v", err)
os.Exit(1)
}
logrus.Debug("Starting webhook manager")
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
logrus.Errorf("Error starting manager: %v", err)
os.Exit(1)
}
}
func runAndReportAudit(c conf.Configuration, auditPath string, outputFile string, outputURL string, outputFormat string) validator.AuditData {
k, err := kube.CreateResourceProvider(auditPath)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
os.Exit(1)
}
auditData, err := validator.RunAudit(c, k)
if err != nil {
panic(err)
}
var outputBytes []byte
if outputFormat == "score" {
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.ClusterSummary.Score))
} else if outputFormat == "yaml" {
jsonBytes, err := json.Marshal(auditData)
if err == nil {
outputBytes, err = yaml.JSONToYAML(jsonBytes)
}
} else {
outputBytes, err = json.MarshalIndent(auditData, "", " ")
}
if err != nil {
logrus.Errorf("Error marshalling audit: %v", err)
os.Exit(1)
}
if outputURL == "" && outputFile == "" {
os.Stdout.Write(outputBytes)
} else {
if outputURL != "" {
req, err := http.NewRequest("POST", outputURL, bytes.NewBuffer(outputBytes))
if err != nil {
logrus.Errorf("Error building request for output: %v", err)
os.Exit(1)
}
if outputFormat == "json" {
req.Header.Set("Content-Type", "application/json")
} else if outputFormat == "yaml" {
req.Header.Set("Content-Type", "application/x-yaml")
} else {
req.Header.Set("Content-Type", "text/plain")
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
logrus.Errorf("Error making request for output: %v", err)
os.Exit(1)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Errorf("Error reading response: %v", err)
os.Exit(1)
}
logrus.Infof("Received response: %v", body)
}
if outputFile != "" {
err := ioutil.WriteFile(outputFile, []byte(outputBytes), 0644)
if err != nil {
logrus.Errorf("Error writing output to file: %v", err)
os.Exit(1)
}
}
}
return auditData
cmd.Execute(Version)
}
+48 -81
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -16,108 +16,56 @@ package config
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
packr "github.com/gobuffalo/packr/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/yaml"
)
// Configuration contains all of the config for the validation checks.
type Configuration struct {
DisplayName string `json:"displayName"`
Resources Resources `json:"resources"`
HealthChecks HealthChecks `json:"healthChecks"`
Images Images `json:"images"`
Networking Networking `json:"networking"`
Security Security `json:"security"`
DisplayName string `json:"displayName"`
Checks map[string]Severity `json:"checks"`
CustomChecks map[string]SchemaCheck `json:"customChecks"`
Exemptions []Exemption `json:"exemptions"`
DisallowExemptions bool `json:"disallowExemptions"`
}
// Resources contains config for resource requests and limits.
type Resources struct {
CPURequestsMissing Severity `json:"cpuRequestsMissing"`
CPURequestRanges ResourceRanges `json:"cpuRequestRanges"`
CPULimitsMissing Severity `json:"cpuLimitsMissing"`
CPULimitRanges ResourceRanges `json:"cpuLimitRanges"`
MemoryRequestsMissing Severity `json:"memoryRequestsMissing"`
MemoryRequestRanges ResourceRanges `json:"memoryRequestRanges"`
MemoryLimitsMissing Severity `json:"memoryLimitsMissing"`
MemoryLimitRanges ResourceRanges `json:"memoryLimitRanges"`
// Exemption represents an exemption to normal rules
type Exemption struct {
Rules []string `json:"rules"`
ControllerNames []string `json:"controllerNames"`
}
// ResourceRanges contains config for requests or limits for a specific resource.
type ResourceRanges struct {
Warning ResourceRange `json:"warning"`
Error ResourceRange `json:"error"`
}
var configBox = (*packr.Box)(nil)
// ResourceRange can contain below and above conditions for validation.
type ResourceRange struct {
Below *resource.Quantity `json:"below"`
Above *resource.Quantity `json:"above"`
}
// HealthChecks contains config for readiness and liveness probes.
type HealthChecks struct {
ReadinessProbeMissing Severity `json:"readinessProbeMissing"`
LivenessProbeMissing Severity `json:"livenessProbeMissing"`
}
// Images contains the config for images.
type Images struct {
TagNotSpecified Severity `json:"tagNotSpecified"`
PullPolicyNotAlways Severity `json:"pullPolicyNotAlways"`
Whitelist ErrorWarningLists `json:"whitelist"`
Blacklist ErrorWarningLists `json:"blacklist"`
}
// ErrorWarningLists provides lists of patterns to match or avoid in image tags.
type ErrorWarningLists struct {
Error []string `json:"error"`
Warning []string `json:"warning"`
}
// Networking contains the config for networking validations.
type Networking struct {
HostNetworkSet Severity `json:"hostNetworkSet"`
HostPortSet Severity `json:"hostPortSet"`
}
// Security contains the config for security validations.
type Security struct {
HostIPCSet Severity `json:"hostIPCSet"`
HostPIDSet Severity `json:"hostPIDSet"`
RunAsRootAllowed Severity `json:"runAsRootAllowed"`
RunAsPrivileged Severity `json:"RunAsPrivileged"`
NotReadOnlyRootFileSystem Severity `json:"notReadOnlyRootFileSystem"`
PrivilegeEscalationAllowed Severity `json:"privilegeEscalationAllowed"`
Capabilities SecurityCapabilities `json:"capabilities"`
}
// SecurityCapabilities contains the config for security capabilities validations.
type SecurityCapabilities struct {
Error SecurityCapabilityLists `json:"error"`
Warning SecurityCapabilityLists `json:"warning"`
}
// SecurityCapabilityLists contains the config for security capabilitie list validations.
type SecurityCapabilityLists struct {
IfAnyAdded []corev1.Capability `json:"ifAnyAdded"`
IfAnyAddedBeyond []corev1.Capability `json:"ifAnyAddedBeyond"`
IfAnyNotDropped []corev1.Capability `json:"ifAnyNotDropped"`
func getConfigBox() *packr.Box {
if configBox == (*packr.Box)(nil) {
configBox = packr.New("Config", "../../examples")
}
return configBox
}
// ParseFile parses config from a file.
func ParseFile(path string) (Configuration, error) {
configBox := packr.New("Config", "../../examples")
var rawBytes []byte
var err error
if path == "" {
rawBytes, err = configBox.Find("config.yaml")
rawBytes, err = getConfigBox().Find("config.yaml")
} else if strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://") {
//path is a url
response, err2 := http.Get(path)
if err2 != nil {
return Configuration{}, err2
}
rawBytes, err = ioutil.ReadAll(response.Body)
} else {
//path is local
rawBytes, err = ioutil.ReadFile(path)
}
if err != nil {
@@ -134,9 +82,28 @@ func Parse(rawBytes []byte) (Configuration, error) {
for {
if err := d.Decode(&conf); err != nil {
if err == io.EOF {
return conf, nil
break
}
return conf, fmt.Errorf("Decoding config failed: %v", err)
}
}
for key, check := range conf.CustomChecks {
err := check.Initialize(key)
if err != nil {
return conf, err
}
conf.CustomChecks[key] = check
if _, ok := conf.Checks[key]; !ok {
return conf, fmt.Errorf("no severity specified for custom check %s. Please add the following to your configuration:\n\nchecks:\n %s: warning # or danger/ignore\n\nto enable your check", key, key)
}
}
return conf, conf.Validate()
}
// Validate checks if a config is valid
func (c Configuration) Validate() error {
if len(c.Checks) == 0 {
return errors.New("No checks were enabled")
}
return nil
}
+134 -101
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -15,133 +15,166 @@
package config
import (
"context"
"io"
"log"
"net/http"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
)
var resourceConfInvalid1 = `test`
var confInvalid = `test`
var resourceConfYAML1 = `---
resources:
cpuRequestRanges:
error:
below: 100m
above: 1
warning:
below: 200m
above: 800m
memoryRequestRanges:
error:
below: 100M
above: 3G
warning:
below: 200M
above: 2G
cpuLimitRanges:
error:
below: 100m
above: 2
warning:
below: 300m
above: 1800m
memoryLimitRanges:
error:
below: 200M
above: 6G
warning:
below: 300M
above: 4G
var confValidYAML = `
checks:
cpuRequestsMissing: warning
`
var resourceConfJSON1 = `{
"resources": {
"cpuRequestRanges": {
"error": {
"below": "100m",
"above": 1
},
"warning": {
"below": "200m",
"above": "800m"
}
},
"memoryRequestRanges": {
"error": {
"below": "100M",
"above": "3G"
},
"warning": {
"below": "200M",
"above": "2G"
}
},
"cpuLimitRanges": {
"error": {
"below": "100m",
"above": 2
},
"warning": {
"below": "300m",
"above": "1800m"
}
},
"memoryLimitRanges": {
"error": {
"below": "200M",
"above": "6G"
},
"warning": {
"below": "300M",
"above": "4G"
}
}
}
}`
var confValidJSON = `
{
"checks": {
"cpuRequestsMissing": "warning"
}
}
`
var confCustomChecks = `
checks:
foo: warning
customChecks:
foo:
successMessage: Security context is set
failureMessage: Security context should be set
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- securityContext
`
var confCustomChecksWithJSONSchema = `
checks:
foo: warning
customChecks:
foo:
successMessage: Security context is set
failureMessage: Security context should be set
category: Security
target: Container
jsonSchema: >
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"required": ["securityContext"]
}
`
var confCustomChecksMissing = `
customChecks:
foo:
successMessage: Security context is set
failureMessage: Security context should be set
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- securityContext
`
func TestParseError(t *testing.T) {
_, err := Parse([]byte(resourceConfInvalid1))
_, err := Parse([]byte(confInvalid))
expectedErr := "Decoding config failed: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type config.Configuration"
assert.EqualError(t, err, expectedErr)
}
func TestParseYaml(t *testing.T) {
parsedConf, err := Parse([]byte(resourceConfYAML1))
parsedConf, err := Parse([]byte(confValidYAML))
assert.NoError(t, err, "Expected no error when parsing YAML config")
testParsedConfig(t, &parsedConf)
}
func TestParseJson(t *testing.T) {
parsedConf, err := Parse([]byte(resourceConfJSON1))
parsedConf, err := Parse([]byte(confValidJSON))
assert.NoError(t, err, "Expected no error when parsing JSON config")
testParsedConfig(t, &parsedConf)
}
func testParsedConfig(t *testing.T, config *Configuration) {
cpuRequests := config.Resources.CPURequestRanges
assert.Equal(t, int64(100), cpuRequests.Error.Below.ScaledValue(resource.Milli))
assert.Equal(t, int64(1000), cpuRequests.Error.Above.ScaledValue(resource.Milli))
assert.Equal(t, int64(200), cpuRequests.Warning.Below.ScaledValue(resource.Milli))
assert.Equal(t, int64(800), cpuRequests.Warning.Above.ScaledValue(resource.Milli))
func TestConfigFromURL(t *testing.T) {
var err error
var parsedConf Configuration
srv := &http.Server{Addr: ":8081"}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, confValidYAML)
})
memRequests := config.Resources.MemoryRequestRanges
assert.Equal(t, int64(100), memRequests.Error.Below.ScaledValue(resource.Mega))
assert.Equal(t, int64(3000), memRequests.Error.Above.ScaledValue(resource.Mega))
assert.Equal(t, int64(200), memRequests.Warning.Below.ScaledValue(resource.Mega))
assert.Equal(t, int64(2000), memRequests.Warning.Above.ScaledValue(resource.Mega))
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("ListenAndServe(): %s", err)
}
}()
time.Sleep(time.Second)
cpuLimits := config.Resources.CPULimitRanges
assert.Equal(t, int64(100), cpuLimits.Error.Below.ScaledValue(resource.Milli))
assert.Equal(t, int64(2000), cpuLimits.Error.Above.ScaledValue(resource.Milli))
assert.Equal(t, int64(300), cpuLimits.Warning.Below.ScaledValue(resource.Milli))
assert.Equal(t, int64(1800), cpuLimits.Warning.Above.ScaledValue(resource.Milli))
parsedConf, err = ParseFile("http://localhost:8081/exampleURL")
assert.NoError(t, err, "Expected no error when parsing YAML from URL")
if err := srv.Shutdown(context.TODO()); err != nil {
panic(err)
}
testParsedConfig(t, &parsedConf)
memLimits := config.Resources.MemoryLimitRanges
assert.Equal(t, int64(200), memLimits.Error.Below.ScaledValue(resource.Mega))
assert.Equal(t, int64(6000), memLimits.Error.Above.ScaledValue(resource.Mega))
assert.Equal(t, int64(300), memLimits.Warning.Below.ScaledValue(resource.Mega))
assert.Equal(t, int64(4000), memLimits.Warning.Above.ScaledValue(resource.Mega))
}
func TestConfigNoServerError(t *testing.T) {
var err error
_, err = ParseFile("http://localhost:8081/exampleURL")
assert.Error(t, err)
assert.Regexp(t, regexp.MustCompile("connection refused"), err.Error())
}
func TestConfigWithCustomChecks(t *testing.T) {
valid := map[string]interface{}{
"securityContext": map[string]interface{}{},
}
invalid := map[string]interface{}{
"notSecurityContext": map[string]interface{}{},
}
parsedConf, err := Parse([]byte(confCustomChecks))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
isValid, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
assert.NoError(t, err)
assert.Equal(t, true, isValid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(valid)
assert.NoError(t, err)
assert.Equal(t, true, isValid)
isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
}
func TestCustomChecksMissingSeverity(t *testing.T) {
_, err := Parse([]byte(confCustomChecksMissing))
assert.Error(t, err, "Expected error when check has no severity set")
}
func testParsedConfig(t *testing.T, config *Configuration) {
assert.Equal(t, SeverityWarning, config.Checks["cpuRequestsMissing"])
assert.Equal(t, Severity(""), config.Checks["cpuLimitsMissing"])
}
+36
View File
@@ -0,0 +1,36 @@
package config
import (
"strings"
)
// IsActionable determines whether a check is actionable given the current configuration
func (conf Configuration) IsActionable(ruleID, controllerName string) bool {
if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
return false
}
if conf.DisallowExemptions {
return true
}
for _, example := range conf.Exemptions {
for _, rule := range example.Rules {
if rule != ruleID {
continue
}
for _, controller := range example.ControllerNames {
if strings.HasPrefix(controllerName, controller) {
return false
}
}
}
if len(example.Rules) == 0 {
for _, controller := range example.ControllerNames {
if strings.HasPrefix(controllerName, controller) {
return false
}
}
}
}
return true
}
+62
View File
@@ -0,0 +1,62 @@
// 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 config
import (
"testing"
"github.com/stretchr/testify/assert"
)
var confExemptRuleTest = `
checks:
ANY: warning
OTHER: warning
exemptions:
- controllerNames:
- test
rules:
- ANY
`
var confExemptTest = `
checks:
ANY: warning
exemptions:
- controllerNames:
- test
`
func TestInclusiveExemption(t *testing.T) {
parsedConf, _ := Parse([]byte(confExemptTest))
applicable := parsedConf.IsActionable("ANY", "test")
applicableOtherController := parsedConf.IsActionable("ANY", "other")
assert.False(t, applicable, "Expected all checks to be exempted when their controller is specified.")
assert.True(t, applicableOtherController, "Expected checks to only be exempted when their controller is specified.")
}
func TestIndividualRuleException(t *testing.T) {
parsedConf, _ := Parse([]byte(confExemptRuleTest))
applicable := parsedConf.IsActionable("ANY", "test")
applicableOtherRule := parsedConf.IsActionable("OTHER", "test")
applicableOtherRuleOtherController := parsedConf.IsActionable("OTHER", "other")
applicableRuleOtherController := parsedConf.IsActionable("ANY", "other")
assert.False(t, applicable, "Expected all checks to be exempted when their controller and rule are specified.")
assert.True(t, applicableOtherRule, "Expected checks to only be exempted when their controller and rule are specified.")
assert.True(t, applicableOtherRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
assert.True(t, applicableRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
}
+192
View File
@@ -0,0 +1,192 @@
package config
import (
"encoding/json"
"fmt"
"github.com/qri-io/jsonschema"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
// TargetKind represents the part of the config to be validated
type TargetKind string
const (
// 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"
)
// SchemaCheck is a Polaris check that runs using JSON Schema
type SchemaCheck struct {
ID string `yaml:"id"`
Category string `yaml:"category"`
SuccessMessage string `yaml:"successMessage"`
FailureMessage string `yaml:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers"`
Containers includeExcludeList `yaml:"containers"`
Target TargetKind `yaml:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget"`
Schema jsonschema.RootSchema `yaml:"schema"`
JSONSchema string `yaml:"jsonSchema"`
}
type resourceMinimum string
type resourceMaximum string
func init() {
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
}
type includeExcludeList struct {
Include []string `yaml:"include"`
Exclude []string `yaml:"exclude"`
}
func newResourceMinimum() jsonschema.Validator {
return new(resourceMinimum)
}
func newResourceMaximum() jsonschema.Validator {
return new(resourceMaximum)
}
// Validate checks that a specified quanitity is not less than the minimum
func (min resourceMinimum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
err := validateRange(path, string(min), data, true)
if err != nil {
*errs = append(*errs, *err...)
}
}
// Validate checks that a specified quanitity is not greater than the maximum
func (max resourceMaximum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
err := validateRange(path, string(max), data, false)
if err != nil {
*errs = append(*errs, *err...)
}
}
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.ValError) {
resStr, ok := i.(string)
if !ok {
return resource.Quantity{}, &[]jsonschema.ValError{
{Message: fmt.Sprintf("Resource quantity %v is not a string", i)},
}
}
q, err := resource.ParseQuantity(resStr)
if err != nil {
return resource.Quantity{}, &[]jsonschema.ValError{
{Message: fmt.Sprintf("Could not parse resource quantity: %s", resStr)},
}
}
return q, nil
}
func validateRange(path string, limit interface{}, data interface{}, isMinimum bool) *[]jsonschema.ValError {
limitQuantity, err := parseQuantity(limit)
if err != nil {
return err
}
actualQuantity, err := parseQuantity(data)
if err != nil {
return err
}
cmp := limitQuantity.Cmp(actualQuantity)
if isMinimum {
if cmp == 1 {
return &[]jsonschema.ValError{
{Message: fmt.Sprintf("%s quantity %v is > %v", path, actualQuantity, limitQuantity)},
}
}
} else {
if cmp == -1 {
return &[]jsonschema.ValError{
{Message: fmt.Sprintf("%s quantity %v is < %v", path, actualQuantity, limitQuantity)},
}
}
}
return nil
}
// Initialize sets up the schema
func (check *SchemaCheck) Initialize(id string) error {
check.ID = id
if check.JSONSchema != "" {
if err := json.Unmarshal([]byte(check.JSONSchema), &check.Schema); err != nil {
return err
}
}
return nil
}
// CheckPod checks a pod spec against the schema
func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
return check.CheckObject(pod)
}
// CheckController checks a controler's spec against the schema
func (check SchemaCheck) CheckController(bytes []byte) (bool, error) {
errs, err := check.Schema.ValidateBytes(bytes)
return len(errs) == 0, err
}
// CheckContainer checks a container spec against the schema
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
return check.CheckObject(container)
}
// CheckObject checks arbitrary data against the schema
func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
bytes, err := json.Marshal(obj)
if err != nil {
return false, err
}
errs, err := check.Schema.ValidateBytes(bytes)
return len(errs) == 0, err
}
// IsActionable decides if this check applies to a particular target
func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool {
if check.Target != target {
return false
}
isIncluded := len(check.Controllers.Include) == 0
for _, inclusion := range check.Controllers.Include {
if inclusion == controllerType {
isIncluded = true
break
}
}
if !isIncluded {
return false
}
for _, exclusion := range check.Controllers.Exclude {
if exclusion == controllerType {
return false
}
}
if check.Target == TargetContainer {
isIncluded := len(check.Containers.Include) == 0
for _, inclusion := range check.Containers.Include {
if (inclusion == "initContainer" && isInit) || (inclusion == "container" && !isInit) {
isIncluded = true
break
}
}
if !isIncluded {
return false
}
for _, exclusion := range check.Containers.Exclude {
if (exclusion == "initContainer" && isInit) || (exclusion == "container" && !isInit) {
return false
}
}
}
return true
}
+4 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -24,11 +24,11 @@ const (
// SeverityWarning warns on validation failures
SeverityWarning Severity = "warning"
// SeverityError errors on validation failures
SeverityError Severity = "error"
// SeverityDanger errors on validation failures
SeverityDanger Severity = "danger"
)
// IsActionable returns true if the severity level is warning or error
func (severity *Severity) IsActionable() bool {
return *severity == SeverityWarning || *severity == SeverityError
return *severity == SeverityWarning || *severity == SeverityDanger
}
+78 -37
View File
@@ -4,6 +4,7 @@
position: relative;
}
.filters .title,
.card h3 {
margin: 0;
font-weight: 300;
@@ -40,11 +41,9 @@
width: 32%;
vertical-align: top;
}
.cluster-overview .cluster-score{
padding-top: 15px;
}
.cluster-overview .cluster-score,
.cluster-overview .result-messages {
padding-top: 70px;
padding-top: 15px;
}
.cluster-overview .graph canvas {
@@ -93,45 +92,24 @@
.cluster-overview .result-messages ul {
font-size: 20px;
line-height: 35px;
line-height: 42px;
}
.cluster-overview-footer {
position: absolute;
bottom: 0px;
right: 10px;
right: 40px;
left: 40px;
}
.cluster-overview-footer p {
display: inline-block;
.cluster-overview-footer .kubernetes-stats {
font-size: 11px;
color: #777;
width: 48%;
}
.cluster-overview-footer p.kubernetes-overview {
text-align: right;
}
.cluster-overview-footer p.kubernetes-overview .kubernetes-stat {
margin-left: 15px;
}
.kubernetes-overview ul {
list-style-type: none;
margin: 0;
padding: 0 10px;
text-align: right;
}
.kubernetes-overview li {
display: inline-block;
margin: 20px 0 0 10px;
}
.kubernetes-overview li span {
margin-right: 3px;
.cluster-overview-footer .kubernetes-stats {
display: flex;
justify-content: space-between;
}
.category .name {
@@ -191,7 +169,7 @@
cursor: pointer;
}
.expandable-table .resource-info .caret-expander {
.resource-info .caret-expander {
display: inline-block;
width: 15px;
height: 15px;
@@ -202,16 +180,16 @@
background-position: 2px center;
}
.expandable-table .resource-info.expanded .caret-expander {
.resource-info.expanded .caret-expander {
background-image: url('../images/caret-bottom.svg');
background-position: center 2px;
}
.expandable-table .resource-info .expandable-content {
.resource-info .expandable-content {
display: none;
}
.expandable-table .resource-info.expanded .expandable-content {
.resource-info.expanded .expandable-content {
display: block;
}
@@ -236,6 +214,26 @@ ul.message-list {
.cluster-overview ul.message-list {
margin: 0px;
text-align: right;
}
.cluster-overview ul.message-list .message-group {
display: inline-block;
min-width: 130px;
}
.cluster-overview ul.message-list .message {
display: block;
font-size: 12px;
line-height: 12px;
}
.cluster-overview ul.message-list li i.message-icon {
font-size: 42px;
text-align: center;
min-width: 42px;
vertical-align: top;
margin-top: 8px;
}
ul.message-list li {
@@ -257,11 +255,11 @@ ul.message-list li i.message-icon {
color: #8BD2DC;
}
.result-messages .warning i.message-icon {
.result-messages .failure.warning i.message-icon {
color: #f26c21;
}
.result-messages .error i.message-icon {
.result-messages .failure.danger i.message-icon {
color: #a11f4c;
}
@@ -319,3 +317,46 @@ a.more-info:hover {
0% {opacity: 0;}
100% {opacity: 1;}
}
.filters .title {
padding-left: 0;
}
.filters .caret-expander {
margin-bottom: 2px;
margin-top: 8px;
}
.filters input[type="checkbox"] {
margin-top: 4px;
}
.filters input[type="submit"] {
padding: 4px 10px;
box-shadow: none;
background-color: #23103A;
border: 1px solid #ccc;
border-radius: 4px;
color: #fff;
cursor: pointer;
font-size: 20px;
width: 100px;
margin-top: 15px;
}
form.namespace-list {
line-height: 20px;
margin: 5px 35px;
padding: 0;
}
.namespace-row {
display: flex;
padding: 3px 0;
}
.namespace-row label {
margin-left: 4px;
font-size: 20px;
}
+12 -11
View File
@@ -22,20 +22,11 @@ body {
}
.navbar .logo {
width: 250px;
}
.navbar span.oss-text {
color: #23103A;
display: block;
font-size: 11px;
margin-bottom: 3px;
padding-left: 66px;
margin-bottom: -8px;
height: 80px;
}
.navbar .fw-logo {
height: 50px;
height: 64px;
}
.navbar a {
@@ -66,3 +57,13 @@ body {
text-decoration: underline;
}
.exemption-alert {
margin-top: 15px;
padding: 15px;
border: 1px solid #f26c21;
border-radius: 2px;
}
.exemption-alert .fa-exclamation {
margin-right: 10px;
color: #f26c21;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+3 -3
View File
@@ -5,9 +5,9 @@ $(function () {
labels: ["Passing", "Warning", "Error"],
datasets: [{
data: [
polarisAuditData.ClusterSummary.Results.Totals.Successes,
polarisAuditData.ClusterSummary.Results.Totals.Warnings,
polarisAuditData.ClusterSummary.Results.Totals.Errors,
polarisSummary.Successes,
polarisSummary.Warnings,
polarisSummary.Dangers,
],
backgroundColor: ['#8BD2DC', '#f26c21', '#a11f4c'],
}]
+22
View File
@@ -0,0 +1,22 @@
$(function () {
// Check selected namespace options on page load
const urlParams = new URLSearchParams(window.location.search);
const currentNamespaces = urlParams.getAll('ns');
currentNamespaces.forEach(ns => {
$(`input#namespace-${ns}`).prop('checked', true);
});
// Handle new filter submissions
$('#namespaceFiltersForm').on('submit', e => {
e.preventDefault();
let newParams = new URLSearchParams();
$('#namespaceFiltersForm input[type="checkbox"]').each((index, checkbox) => {
if (checkbox.checked) {
newParams.append('ns', checkbox.name);
}
});
window.location = new URL(`?${newParams.toString()}`, window.location).toString();
});
});
+104 -52
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -19,13 +19,15 @@ import (
"encoding/json"
"html/template"
"net/http"
"net/url"
"path"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/validator"
packr "github.com/gobuffalo/packr/v2"
"github.com/gorilla/mux"
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/pkg/kube"
"github.com/reactiveops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
"gitlab.com/golang-commonmark/markdown"
)
@@ -72,30 +74,32 @@ func GetTemplateBox() *packr.Box {
// GetMarkdownBox returns a binary-friendly set of markdown files with error details
func GetMarkdownBox() *packr.Box {
if markdownBox == (*packr.Box)(nil) {
markdownBox = packr.New("Markdown", "../../docs")
markdownBox = packr.New("Markdown", "../../docs/check-documentation")
}
return markdownBox
}
// templateData is passed to the dashboard HTML template
type templateData struct {
BasePath string
AuditData validator.AuditData
JSON template.JS
BasePath string
Config config.Configuration
AuditData validator.AuditData
FilteredAuditData validator.AuditData
JSON template.JS
}
// GetBaseTemplate puts together the dashboard template. Individual pieces can be overridden before rendering.
func GetBaseTemplate(name string) (*template.Template, error) {
tmpl := template.New(name).Funcs(template.FuncMap{
"getWarningWidth": getWarningWidth,
"getSuccessWidth": getSuccessWidth,
"getWeatherIcon": getWeatherIcon,
"getWeatherText": getWeatherText,
"getGrade": getGrade,
"getIcon": getIcon,
"getCategoryLink": getCategoryLink,
"getCategoryInfo": getCategoryInfo,
"getAllControllerResults": getAllControllerResults,
"getWarningWidth": getWarningWidth,
"getSuccessWidth": getSuccessWidth,
"getWeatherIcon": getWeatherIcon,
"getWeatherText": getWeatherText,
"getGrade": getGrade,
"getIcon": getIcon,
"getResultClass": getResultClass,
"getCategoryLink": getCategoryLink,
"getCategoryInfo": getCategoryInfo,
})
templateFileNames := []string{
@@ -135,12 +139,38 @@ func writeTemplate(tmpl *template.Template, data *templateData, w http.ResponseW
buf.WriteTo(w)
}
func getConfigForQuery(base config.Configuration, query url.Values) config.Configuration {
c := base
exemptions := query.Get("disallowExemptions")
if exemptions == "false" {
c.DisallowExemptions = false
}
if exemptions == "true" {
c.DisallowExemptions = true
}
return c
}
func stripUnselectedNamespaces(data *validator.AuditData, selectedNamespaces []string) {
newResults := []validator.ControllerResult{}
for _, res := range data.Results {
if stringInSlice(res.Namespace, selectedNamespaces) {
newResults = append(newResults, res)
}
}
data.Results = newResults
}
// GetRouter returns a mux router serving all routes necessary for the dashboard
func GetRouter(c conf.Configuration, auditPath string, port int, basePath string) *mux.Router {
router := mux.NewRouter()
func GetRouter(c config.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) *mux.Router {
router := mux.NewRouter().PathPrefix(basePath).Subrouter()
fileServer := http.FileServer(GetAssetBox())
router.PathPrefix("/static/").Handler(http.StripPrefix(path.Join(basePath, "/static/"), fileServer))
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
router.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
favicon, err := GetAssetBox().Find("favicon-32x32.png")
if err != nil {
@@ -150,58 +180,86 @@ func GetRouter(c conf.Configuration, auditPath string, port int, basePath string
}
w.Write(favicon)
})
router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) {
k, err := kube.CreateResourceProvider(auditPath)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
return
adjustedConf := getConfigForQuery(c, r.URL.Query())
if auditData == nil {
k, err := kube.CreateResourceProvider(auditPath, "")
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
return
}
auditDataObj, err := validator.RunAudit(adjustedConf, k)
if err != nil {
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
return
}
auditData = &auditDataObj
}
JSONHandler(w, r, c, k)
JSONHandler(w, r, auditData)
})
router.HandleFunc("/details/{category}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
category := vars["category"]
category = strings.Replace(category, ".md", "", -1)
DetailsHandler(w, r, category, basePath)
})
fileServer := http.FileServer(GetAssetBox())
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fileServer))
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
if r.URL.Path != "/" && r.URL.Path != basePath {
http.NotFound(w, r)
return
}
k, err := kube.CreateResourceProvider(auditPath)
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
return
adjustedConf := getConfigForQuery(c, r.URL.Query())
if auditData == nil {
k, err := kube.CreateResourceProvider(auditPath, "")
if err != nil {
logrus.Errorf("Error fetching Kubernetes resources %v", err)
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
return
}
auditData, err := validator.RunAudit(adjustedConf, k)
if err != nil {
logrus.Errorf("Error getting audit data: %v", err)
http.Error(w, "Error running audit", 500)
return
}
MainHandler(w, r, adjustedConf, auditData, basePath)
} else {
MainHandler(w, r, adjustedConf, *auditData, basePath)
}
auditData, err := validator.RunAudit(c, k)
if err != nil {
logrus.Errorf("Error getting audit data: %v", err)
http.Error(w, "Error running audit", 500)
return
}
MainHandler(w, r, auditData, basePath)
})
return router
}
// MainHandler gets template data and renders the dashboard with it.
func MainHandler(w http.ResponseWriter, r *http.Request, auditData validator.AuditData, basePath string) {
jsonData, err := json.Marshal(auditData)
func MainHandler(w http.ResponseWriter, r *http.Request, c config.Configuration, auditData validator.AuditData, basePath string) {
jsonData, err := json.Marshal(auditData.GetSummary())
if err != nil {
http.Error(w, "Error serializing audit data", 500)
return
}
filteredAuditData := auditData
namespaces := r.URL.Query()["ns"]
if len(namespaces) > 0 {
stripUnselectedNamespaces(&filteredAuditData, namespaces)
}
data := templateData{
BasePath: basePath,
AuditData: auditData,
JSON: template.JS(jsonData),
BasePath: basePath,
AuditData: auditData,
FilteredAuditData: filteredAuditData,
JSON: template.JS(jsonData),
Config: c,
}
tmpl, err := GetBaseTemplate("main")
if err != nil {
@@ -213,13 +271,7 @@ func MainHandler(w http.ResponseWriter, r *http.Request, auditData validator.Aud
}
// JSONHandler gets template data and renders json with it.
func JSONHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, kubeResources *kube.ResourceProvider) {
auditData, err := validator.RunAudit(c, kubeResources)
if err != nil {
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
return
}
func JSONHandler(w http.ResponseWriter, r *http.Request, auditData *validator.AuditData) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(auditData)
+38 -15
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -16,23 +16,28 @@ package dashboard
import (
"fmt"
"github.com/reactiveops/polaris/pkg/validator"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/validator"
)
func getAllControllerResults(nr validator.NamespaceResult) []validator.ControllerResult {
results := []validator.ControllerResult{}
results = append(results, nr.DeploymentResults...)
results = append(results, nr.StatefulSetResults...)
return results
}
func getWarningWidth(counts validator.CountSummary, fullWidth int) uint {
return uint(float64(counts.Successes+counts.Warnings) / float64(counts.Successes+counts.Warnings+counts.Errors) * float64(fullWidth))
denom := counts.Successes + counts.Warnings + counts.Dangers
if denom == 0 {
return uint(0)
}
res := float64(counts.Successes+counts.Warnings) / float64(denom) * float64(fullWidth)
return uint(res)
}
func getSuccessWidth(counts validator.CountSummary, fullWidth int) uint {
return uint(float64(counts.Successes) / float64(counts.Successes+counts.Warnings+counts.Errors) * float64(fullWidth))
denom := counts.Successes + counts.Warnings + counts.Dangers
if denom == 0 {
return uint(0)
}
res := float64(counts.Successes) / float64(denom) * float64(fullWidth)
return uint(res)
}
func getGrade(counts validator.CountSummary) string {
@@ -81,6 +86,16 @@ func getWeatherIcon(counts validator.CountSummary) string {
}
}
func getResultClass(result validator.ResultMessage) string {
cls := string(result.Severity)
if result.Success {
cls += " success"
} else {
cls += " failure"
}
return cls
}
func getWeatherText(counts validator.CountSummary) string {
score := counts.GetScore()
if score >= 90 {
@@ -97,12 +112,11 @@ func getWeatherText(counts validator.CountSummary) string {
}
func getIcon(rm validator.ResultMessage) string {
switch rm.Type {
case "success":
if rm.Success {
return "fas fa-check"
case "warning":
} else if rm.Severity == config.SeverityWarning {
return "fas fa-exclamation"
default:
} else {
return "fas fa-times"
}
}
@@ -157,3 +171,12 @@ func getCategoryInfo(category string) string {
return ""
}
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
+101 -42
View File
@@ -9,10 +9,13 @@
<div class="cluster-overview">
<div class="cluster-score">
<div class="score-details">
<div class="weather"><i class="fas {{ getWeatherIcon .AuditData.ClusterSummary.Results.Totals }}"></i></div>
<div class="sailing">{{ getWeatherText .AuditData.ClusterSummary.Results.Totals }}</div>
<div class="scores"><span>Grade: </span><strong>{{ getGrade .AuditData.ClusterSummary.Results.Totals }}</strong></div>
<div class="scores"><span>Score: </span><strong>{{ .AuditData.ClusterSummary.Results.Totals.GetScore }}%</strong></div>
<div class="weather"><i class="fas {{ getWeatherIcon .AuditData.GetSummary }}"></i></div>
<div class="sailing">{{ getWeatherText .FilteredAuditData.GetSummary }}</div>
<div class="scores"><span>Grade: </span><strong>{{ getGrade .FilteredAuditData.GetSummary }}</strong></div>
<div class="scores"><span>Score: </span><strong>{{ .FilteredAuditData.GetSummary.GetScore }}%</strong></div>
<p class="score-description">
Score is the percentage of passing checks. Warnings get half the weight of dangerous checks.
</p>
</div>
</div>
<div class="graph">
@@ -20,41 +23,60 @@
</div>
<div class="result-messages">
<ul class="message-list">
<li class="success"><i class="message-icon fas fa-check"></i> {{ .AuditData.ClusterSummary.Results.Totals.Successes }} checks passed</li>
<li class="warning"><i class="message-icon fas fa-exclamation"></i> {{ .AuditData.ClusterSummary.Results.Totals.Warnings }} checks had warnings</li>
<li class="error"><i class="message-icon fas fa-times"></i> {{ .AuditData.ClusterSummary.Results.Totals.Errors }} checks had errors</li>
<li class="success">
<i class="message-icon fas fa-check"></i>
<div class="message-group">
<span class="count"> {{ .FilteredAuditData.GetSummary.Successes }}</span>
<span class="message"> passing checks</span>
</div>
</li>
<li class="failure warning">
<i class="message-icon fas fa-exclamation"></i>
<div class="message-group">
<span class="count"> {{ .FilteredAuditData.GetSummary.Warnings }}</span>
<span class="message"> warning checks</span>
</div>
</li>
<li class="failure danger">
<i class="message-icon fas fa-times"></i>
<div class="message-group">
<span class="count"> {{ .FilteredAuditData.GetSummary.Dangers }}</span>
<span class="message"> dangerous checks</span>
</div>
</li>
</ul>
</div>
</div>
<div class="cluster-overview-footer">
<p class="score-description">
Score is the percentage of passing checks. Warnings get half the weight of errors.
</p>
<p class="kubernetes-overview">
<span class="kubernetes-stat">
<div class="kubernetes-stats">
<div class="kubernetes-stat">
<span>Kubernetes Version:</span>
<strong>{{.AuditData.ClusterSummary.Version}}</strong>
</span>
<span class="kubernetes-stat">
<strong>{{.FilteredAuditData.ClusterInfo.Version}}</strong>
</div>
<div class="kubernetes-stat">
<span>Nodes:</span>
<strong>{{.AuditData.ClusterSummary.Nodes}}</strong>
</span>
<span class="kubernetes-stat">
<span>Pods:</span>
<strong>{{.AuditData.ClusterSummary.Pods}}</strong>
</span>
<span class="kubernetes-stat">
<strong>{{.FilteredAuditData.ClusterInfo.Nodes}}</strong>
</div>
<div class="kubernetes-stat">
<span>Namespaces:</span>
<strong>{{.AuditData.ClusterSummary.Namespaces}}</strong>
</span>
</p>
<strong>{{.FilteredAuditData.ClusterInfo.Namespaces}}</strong>
</div>
<div class="kubernetes-stat">
<span>Controllers:</span>
<strong>{{.FilteredAuditData.ClusterInfo.Controllers}}</strong>
</div>
<div class="kubernetes-stat">
<span>Pods:</span>
<strong>{{.FilteredAuditData.ClusterInfo.Pods}}</strong>
</div>
</div>
</div>
</div>
<div class="card category">
<h3>Results by Category</h3>
<div class="expandable-table">
{{ range $category, $summary := .AuditData.ClusterSummary.Results.ByCategory }}
{{ range $category, $summary := .FilteredAuditData.GetSummaryByCategory }}
<div class="resource-info">
<div class="status-bar">
<div class="status">
@@ -66,7 +88,7 @@
</div>
</div>
<div class="name"><span class="caret-expander"></span>{{ $category }}<span class="category-score">Score: <strong>{{ $summary.GetScore }}%</strong></span></div>
<div class="result-messages expandable-content">
<div class="result-messages expandable-content">
<p class="category-info">{{ getCategoryInfo $category }} Refer to the <a href="details/{{ getCategoryLink $category }}">Polaris documentation about {{ $category }}</a> for more information.</p>
</div>
</div>
@@ -74,30 +96,51 @@
</div>
</div>
{{ range $namespace, $nsResult := .AuditData.NamespacedResults }}
<div class="card filters">
<div class="resource-info">
<div class="name">
<span class="caret-expander"></span>
<span class="title">Filter by Namespace</span>
</div>
<div class="result-messages expandable-content">
<form id="namespaceFiltersForm" class="namespace-list">
{{ range $namespace, $ctrlResults := .AuditData.GetResultsByNamespace }}
<div class="namespace-row">
<input type="checkbox" name="{{ $namespace }}" id="namespace-{{ $namespace }}">
<label for="namespace-{{ $namespace }}">{{ $namespace }}</label>
</div>
{{ end }}
<input type="submit" value="Apply">
</form>
</div>
</div>
</div>
{{ range $namespace, $ctrlResults := .FilteredAuditData.GetResultsByNamespace }}
<div class="card namespace">
<h3>Namespace: <strong>{{ $namespace }}</strong></h3>
<div class="expandable-table">
{{ range getAllControllerResults $nsResult }}
{{ range $ctrlResults }}
<div class="resource-info">
<div class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: {{ getWarningWidth .PodResult.Summary.Totals 200 }}px;">
<div class="passing" style="width: {{ getSuccessWidth .PodResult.Summary.Totals 200 }}px;"></div>
<div class="warning" style="width: {{ getWarningWidth .PodResult.GetSummary 200 }}px;">
<div class="passing" style="width: {{ getSuccessWidth .PodResult.GetSummary 200 }}px;"></div>
</div>
</div>
</div>
</div>
<div class="name"><span class="caret-expander"></span>
<span class="controller-type">{{ .Type }}:</span>
<span class="controller-type">{{ .Kind }}:</span>
<strong>{{ .Name }}</strong></div>
<div class="result-messages expandable-content">
<h4>Pod Spec:</h4>
<h4>Controller Spec:</h4>
<ul class="message-list">
{{ range $message := .PodResult.Messages}}
<li class="{{ .Type }}">
{{ range $message := .Results.GetSortedResults }}
<li class="{{ getResultClass . }}">
<i class="message-icon {{ getIcon $message }}"></i>
<span class="message">{{ .Message }}</span>
<a class="more-info" href="details/{{ getCategoryLink .Category }}">
@@ -107,12 +150,28 @@
{{ end }}
</ul>
</div>
{{ range .PodResult.ContainerResults}}
<div class="result-messages expandable-content">
<h4>Pod Spec:</h4>
<ul class="message-list">
{{ range $message := .PodResult.Results.GetSortedResults }}
<li class="{{ getResultClass . }}">
<i class="message-icon {{ getIcon $message }}"></i>
<span class="message">{{ .Message }}</span>
<a class="more-info" href="details/{{ getCategoryLink .Category }}">
<i class="far fa-question-circle"></i>
</a>
</li>
{{ end }}
</ul>
</div>
{{ range .PodResult.ContainerResults }}
<div class="result-messages expandable-content">
<h4>Container: {{ .Name }}</h4>
<ul class="message-list">
{{ range $message := .Messages}}
<li class="{{ .Type }}">
{{ range $message := .Results.GetSortedResults }}
<li class="{{ getResultClass . }}">
<i class="message-icon {{ getIcon $message }}"></i>
<span class="message">{{ .Message }}</span>
<a class="more-info" href="details/{{ getCategoryLink .Category }}">
@@ -124,10 +183,10 @@
</div>
{{ end }} {{/* end range .PodResult.ContainerResults */}}
</div>
{{ end }} {{/* end range .DeploymentResults */}}
{{ end }} {{/* end range .Results.GetSortedResults */}}
</div>
</div>
{{ end }} {{/* end range .AuditData.NamespacedResults */}}
<script src="static/js/charts.js">
</script>
{{ end }} {{/* end range .AuditData.GetResultsByNamespace */}}
<script src="static/js/charts.js"></script>
<script src="static/js/filter.js"></script>
{{end}}
+1 -1
View File
@@ -1,5 +1,5 @@
{{define "footer"}}
<div class="footer">
<a href="https://fairwinds.com?source=polaris" target="_blank">&copy;2019 FairwindsOps Inc.</a>
<a href="https://fairwinds.com?source=polaris" target="_blank">&copy;2020 FairwindsOps Inc.</a>
</div>
{{end}}
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<script>
window.polarisAuditData = {{ .JSON }};
window.polarisSummary = {{ .JSON }};
</script>
{{ template "head" . }}
</head>
+12 -3
View File
@@ -1,15 +1,24 @@
{{define "navbar"}}
<div class="navbar">
<div class="navbar-content">
<a href="/">
<a href="{{ .BasePath }}">
<img class="logo" src="static/images/polaris-logo.png" alt="Polaris" />
</a>
<div class="navbar-right">
<a href="https://fairwinds.com?source=polaris" target="_blank">
<span class="oss-text">An Open Source Project By</span>
<img class="fw-logo" src="static/images/fw-logo.svg" alt="Fairwinds" />
<img class="fw-logo" src="static/images/oss-logo.png" alt="Fairwinds" />
</a>
</div>
{{ if and (not .Config.DisallowExemptions) (gt (len .Config.Exemptions) 0) }}
<div class="exemption-alert">
<i class="fas fa-exclamation"></i>
<span>
Some checks were skipped based on configured exemptions.
<a href="?disallowExemptions=true">Click here</a>
to view the report with these checks included.
</span>
</div>
{{ end }}
</div>
</div>
{{end}}
+203 -64
View File
@@ -2,6 +2,8 @@ package kube
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -10,12 +12,15 @@ import (
"time"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Required for other auth providers like GKE.
"k8s.io/client-go/restmapper"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
@@ -26,24 +31,88 @@ type ResourceProvider struct {
SourceName string
SourceType string
Nodes []corev1.Node
Deployments []appsv1.Deployment
StatefulSets []appsv1.StatefulSet
Namespaces []corev1.Namespace
Pods []corev1.Pod
Controllers []GenericWorkload
}
type k8sResource struct {
Kind string `yaml:"kind"`
}
var podSpecFields = []string{"jobTemplate", "spec", "template"}
// CreateResourceProvider returns a new ResourceProvider object to interact with k8s resources
func CreateResourceProvider(directory string) (*ResourceProvider, error) {
func CreateResourceProvider(directory, workload string) (*ResourceProvider, error) {
if workload != "" {
return CreateResourceProviderFromWorkload(workload)
}
if directory != "" {
return CreateResourceProviderFromPath(directory)
}
return CreateResourceProviderFromCluster()
}
// CreateResourceProviderFromWorkload creates a new ResourceProvider that just contains one workload
func CreateResourceProviderFromWorkload(workload string) (*ResourceProvider, error) {
kubeConf, configError := config.GetConfig()
if configError != nil {
logrus.Errorf("Error fetching KubeConfig: %v", configError)
return nil, configError
}
kube, err := kubernetes.NewForConfig(kubeConf)
if err != nil {
logrus.Errorf("Error creating Kubernetes client: %v", err)
return nil, err
}
serverVersion, err := kube.Discovery().ServerVersion()
if err != nil {
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{},
}
parts := strings.Split(workload, "/")
if len(parts) != 4 {
return nil, fmt.Errorf("Invalid workload identifier %s. Should be in format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend", workload)
}
namespace := parts[0]
kind := parts[1]
version := parts[2]
name := parts[3]
dynamicInterface, err := dynamic.NewForConfig(kubeConf)
if err != nil {
logrus.Errorf("Error connecting to dynamic interface: %v", err)
return nil, err
}
groupResources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
logrus.Errorf("Error getting API Group resources: %v", err)
return nil, err
}
restMapper := restmapper.NewDiscoveryRESTMapper(groupResources)
obj, err := getObject(namespace, kind, version, name, &dynamicInterface, &restMapper)
if err != nil {
logrus.Errorf("Could not find workload %s: %v", workload, err)
return nil, err
}
workloadObj, err := NewGenericWorkloadFromUnstructured(kind, obj)
if err != nil {
logrus.Errorf("Could not parse workload %s: %v", workload, err)
return nil, err
}
resources.Controllers = []GenericWorkload{workloadObj}
return &resources, nil
}
// CreateResourceProviderFromPath returns a new ResourceProvider using the YAML files in a directory
func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error) {
resources := ResourceProvider{
@@ -51,14 +120,18 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
SourceType: "Path",
SourceName: directory,
Nodes: []corev1.Node{},
Deployments: []appsv1.Deployment{},
StatefulSets: []appsv1.StatefulSet{},
Namespaces: []corev1.Namespace{},
Pods: []corev1.Pod{},
Controllers: []GenericWorkload{},
}
addYaml := func(contents string) error {
return addResourceFromString(contents, &resources)
if directory == "-" {
fi, err := os.Stdin.Stat()
if err == nil && fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
if err := addResourcesFromReader(os.Stdin, &resources); err != nil {
return nil, err
}
return &resources, nil
}
}
visitFile := func(path string, f os.FileInfo, err error) error {
@@ -67,21 +140,10 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
}
contents, err := ioutil.ReadFile(path)
if err != nil {
logrus.Errorf("Error reading file %v", path)
logrus.Errorf("Error reading file: %v", path)
return err
}
specs := regexp.MustCompile("\n-+\n").Split(string(contents), -1)
for _, spec := range specs {
if strings.TrimSpace(spec) == "" {
continue
}
err = addYaml(spec)
if err != nil {
logrus.Errorf("Error parsing YAML %v", err)
return err
}
}
return nil
return addResourcesFromYaml(string(contents), &resources)
}
err := filepath.Walk(directory, visitFile)
@@ -95,48 +157,59 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
func CreateResourceProviderFromCluster() (*ResourceProvider, error) {
kubeConf, configError := config.GetConfig()
if configError != nil {
logrus.Errorf("Error fetching KubeConfig %v", configError)
logrus.Errorf("Error fetching KubeConfig: %v", configError)
return nil, configError
}
api, err := kubernetes.NewForConfig(kubeConf)
if err != nil {
logrus.Errorf("Error creating Kubernetes client %v", err)
logrus.Errorf("Error creating Kubernetes client: %v", err)
return nil, err
}
return CreateResourceProviderFromAPI(api, kubeConf.Host)
dynamicInterface, err := dynamic.NewForConfig(kubeConf)
if err != nil {
logrus.Errorf("Error connecting to dynamic interface: %v", err)
return nil, err
}
return CreateResourceProviderFromAPI(api, kubeConf.Host, &dynamicInterface)
}
// CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface
func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string) (*ResourceProvider, error) {
func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface) (*ResourceProvider, error) {
listOpts := metav1.ListOptions{}
serverVersion, err := kube.Discovery().ServerVersion()
if err != nil {
logrus.Errorf("Error fetching Cluster API version %v", err)
return nil, err
}
deploys, err := kube.AppsV1().Deployments("").List(listOpts)
if err != nil {
logrus.Errorf("Error fetching Deployments %v", err)
return nil, err
}
statefulSets, err := kube.AppsV1().StatefulSets("").List(listOpts)
if err != nil {
logrus.Errorf("Error fetching StatefulSets%v", err)
logrus.Errorf("Error fetching Cluster API version: %v", err)
return nil, err
}
nodes, err := kube.CoreV1().Nodes().List(listOpts)
if err != nil {
logrus.Errorf("Error fetching Nodes %v", err)
logrus.Errorf("Error fetching Nodes: %v", err)
return nil, err
}
namespaces, err := kube.CoreV1().Namespaces().List(listOpts)
if err != nil {
logrus.Errorf("Error fetching Namespaces %v", err)
logrus.Errorf("Error fetching Namespaces: %v", err)
return nil, err
}
pods, err := kube.CoreV1().Pods("").List(listOpts)
if err != nil {
logrus.Errorf("Error fetching Pods %v", err)
logrus.Errorf("Error fetching Pods: %v", err)
return nil, err
}
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
logrus.Errorf("Error getting API Group resources: %v", err)
return nil, err
}
restMapper := restmapper.NewDiscoveryRESTMapper(resources)
objectCache := map[string]unstructured.Unstructured{}
controllers, err := LoadControllers(pods.Items, dynamic, &restMapper, objectCache)
if err != nil {
logrus.Errorf("Error loading controllers from pods: %v", err)
return nil, err
}
@@ -145,46 +218,112 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string
SourceType: "Cluster",
SourceName: clusterName,
CreationTime: time.Now(),
Deployments: deploys.Items,
StatefulSets: statefulSets.Items,
Nodes: nodes.Items,
Namespaces: namespaces.Items,
Pods: pods.Items,
Controllers: controllers,
}
return &api, nil
}
// LoadControllers loads a list of controllers from the kubeResources Pods
func LoadControllers(pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericWorkload, error) {
interfaces := []GenericWorkload{}
deduped := map[string]corev1.Pod{}
for _, pod := range pods {
owners := pod.ObjectMeta.OwnerReferences
if len(owners) == 0 {
deduped[pod.ObjectMeta.Namespace+"/Pod/"+pod.ObjectMeta.Name] = pod
continue
}
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod
}
for _, pod := range deduped {
workload, err := NewGenericWorkload(pod, dynamicClientPointer, restMapperPointer, objectCache)
if err != nil {
return nil, err
}
interfaces = append(interfaces, workload)
}
return deduplicateControllers(interfaces), nil
}
// Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates.
// In cases like CronJobs older children can hang around, so this takes the most recent.
func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkload {
controllerMap := make(map[string]GenericWorkload)
for _, controller := range inputControllers {
key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName()
oldController, ok := controllerMap[key]
if !ok || controller.ObjectMeta.GetCreationTimestamp().Time.After(oldController.ObjectMeta.GetCreationTimestamp().Time) {
controllerMap[key] = controller
}
}
results := make([]GenericWorkload, 0)
for _, controller := range controllerMap {
results = append(results, controller)
}
return results
}
func addResourcesFromReader(reader io.Reader, resources *ResourceProvider) error {
contents, err := ioutil.ReadAll(reader)
if err != nil {
logrus.Errorf("Error reading from %v: %v", reader, err)
return err
}
if err := addResourcesFromYaml(string(contents), resources); err != nil {
return err
}
return nil
}
func addResourcesFromYaml(contents string, resources *ResourceProvider) error {
specs := regexp.MustCompile("[\r\n]-+[\r\n]").Split(string(contents), -1)
for _, spec := range specs {
if strings.TrimSpace(spec) == "" {
continue
}
err := addResourceFromString(spec, resources)
if err != nil {
logrus.Errorf("Error parsing YAML: (%v)", err)
return err
}
}
return nil
}
func addResourceFromString(contents string, resources *ResourceProvider) error {
contentBytes := []byte(contents)
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
resource := k8sResource{}
err := decoder.Decode(&resource)
if err != nil {
// TODO: should we panic if the YAML is bad?
logrus.Errorf("Invalid YAML: %s", string(contents))
return nil
}
decoder = k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
if resource.Kind == "Deployment" {
dep := appsv1.Deployment{}
err = decoder.Decode(&dep)
resources.Deployments = append(resources.Deployments, dep)
} else if resource.Kind == "StatefulSet" {
dep := appsv1.StatefulSet{}
err = decoder.Decode(&dep)
resources.StatefulSets = append(resources.StatefulSets, dep)
} else if resource.Kind == "Namespace" {
if err != nil {
logrus.Errorf("Invalid YAML: %s", string(contents))
return err
}
if resource.Kind == "Namespace" {
ns := corev1.Namespace{}
err = decoder.Decode(&ns)
resources.Namespaces = append(resources.Namespaces, ns)
} else if resource.Kind == "Pod" {
pod := corev1.Pod{}
err = decoder.Decode(&pod)
resources.Pods = append(resources.Pods, pod)
if err != nil {
return err
}
workload, err := NewGenericWorkloadFromPod(pod, pod)
if err != nil {
return err
}
resources.Controllers = append(resources.Controllers, workload)
} else {
newController, err := GetWorkloadFromBytes(contentBytes)
if err != nil || newController == nil {
return err
}
resources.Controllers = append(resources.Controllers, *newController)
}
if err != nil {
logrus.Errorf("Error parsing %s: %v", resource.Kind, err)
return err
}
return nil
return err
}
+51 -19
View File
@@ -1,10 +1,14 @@
package kube
import (
"github.com/reactiveops/polaris/test"
"github.com/stretchr/testify/assert"
"bytes"
"io/ioutil"
"testing"
"time"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
func TestGetResourcesFromPath(t *testing.T) {
@@ -19,18 +23,16 @@ func TestGetResourcesFromPath(t *testing.T) {
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(resources.Deployments), "Should have a deployment")
assert.Equal(t, "ubuntu", resources.Deployments[0].Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, 1, len(resources.StatefulSets), "Should have a stateful set")
assert.Equal(t, "nginx", resources.StatefulSets[0].Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, 1, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "two", resources.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, 2, len(resources.Pods), "Should have two pods")
assert.Equal(t, "", resources.Pods[0].ObjectMeta.Namespace, "Should have one pod in default namespace")
assert.Equal(t, "two", resources.Pods[1].ObjectMeta.Namespace, "Should have one pod in namespace 'two'")
assert.Equal(t, 8, len(resources.Controllers), "Should have eight controllers")
namespaceCount := map[string]int{}
for _, controller := range resources.Controllers {
namespaceCount[controller.ObjectMeta.GetNamespace()]++
}
assert.Equal(t, 7, namespaceCount[""], "Should have seven controller in default namespace")
assert.Equal(t, 1, namespaceCount["two"], "Should have one controller in namespace 'two'")
}
func TestGetMultipleResourceFromSingleFile(t *testing.T) {
@@ -45,8 +47,38 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(resources.Deployments), "Should have a deployment")
assert.Equal(t, "dashboard", resources.Deployments[0].Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, 1, len(resources.Controllers), "Should have one controller")
assert.Equal(t, "dashboard", resources.Controllers[0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].ObjectMeta.Name)
}
func TestGetMultipleResourceFromBadFile(t *testing.T) {
_, err := CreateResourceProviderFromPath("./test_files/test_3")
assert.NotEqual(t, nil, err, "CreateResource From Path should fail with bad yaml")
}
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)
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, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
@@ -54,9 +86,11 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
}
func TestGetResourceFromAPI(t *testing.T) {
k8s := test.SetupTestAPI()
k8s, dynamicInterface := test.SetupTestAPI()
k8s = test.SetupAddControllers(k8s, "test")
resources, err := CreateResourceProviderFromAPI(k8s, "test")
// TODO find a way to mock out the dynamic client
// and create fake pods in order to find all of the controllers.
resources, err := CreateResourceProviderFromAPI(k8s, "test", &dynamicInterface)
assert.Equal(t, nil, err, "Error should be nil")
assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path")
@@ -64,9 +98,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, 1, len(resources.Deployments), "Should have a deployment")
assert.Equal(t, 1, len(resources.StatefulSets), "Should have a stateful set")
assert.Equal(t, 0, len(resources.Pods), "Should have a pod")
assert.Equal(t, 1, len(resources.Controllers), "Should have 1 controller")
assert.Equal(t, "", resources.Deployments[0].ObjectMeta.Name)
assert.Equal(t, "", resources.Controllers[0].ObjectMeta.GetName())
}
+16
View File
@@ -0,0 +1,16 @@
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: test
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: test
image: busybox
args:
- whoami
restartPolicy: OnFailure
@@ -0,0 +1,20 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test
labels:
k8s-app: test
spec:
selector:
matchLabels:
name: test
template:
metadata:
labels:
name: test
spec:
containers:
- name: test
image: busybox
command: ["whoami"]
resources: {}
+12
View File
@@ -0,0 +1,12 @@
apiVersion: batch/v1
kind: Job
metadata:
name: test
spec:
template:
spec:
containers:
- name: test
image: alpine
command: ["whoami"]
restartPolicy: Never
@@ -0,0 +1,19 @@
apiVersion: v1
kind: ReplicationController
metadata:
name: test
spec:
replicas: 1
selector:
app: test
template:
metadata:
name: test
labels:
app: test
spec:
containers:
- name: test
image: busybox
command: ["tail"]
args: ["-f", "/dev/null"]
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: test-service
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app: test
+5
View File
@@ -0,0 +1,5 @@
--
# Source: polaris/templates/dashboard.deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
::::
+228
View File
@@ -0,0 +1,228 @@
package kube
import (
"bytes"
"encoding/json"
"fmt"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
kubeAPICoreV1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
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 {
Kind string
PodSpec kubeAPICoreV1.PodSpec
ObjectMeta kubeAPIMetaV1.Object
OriginalObjectJSON []byte
}
// NewGenericWorkloadFromUnstructured creates a workload from an unstructured.Unstructured
func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstructured) (GenericWorkload, error) {
workload := GenericWorkload{
Kind: kind,
}
objMeta, err := meta.Accessor(unst)
if err != nil {
return workload, err
}
workload.ObjectMeta = objMeta
b, err := json.Marshal(unst)
if err != nil {
return workload, err
}
workload.OriginalObjectJSON = b
m := make(map[string]interface{})
err = json.Unmarshal(b, &m)
if err != nil {
return workload, err
}
podSpecMap := GetPodSpec(m)
b, err = json.Marshal(podSpecMap)
if err != nil {
return workload, err
}
podSpec := kubeAPICoreV1.PodSpec{}
err = json.Unmarshal(b, &podSpec)
if err != nil {
return workload, err
}
workload.PodSpec = podSpec
return workload, nil
}
// NewGenericWorkloadFromPod builds a new workload for a given Pod without looking at parents
func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericWorkload, error) {
workload := GenericWorkload{
Kind: "Pod",
PodSpec: podResource.Spec,
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
}
if originalObject != nil {
bytes, err := json.Marshal(originalObject)
if err != nil {
return workload, err
}
workload.OriginalObjectJSON = bytes
}
return workload, nil
}
// NewGenericWorkload builds a new workload for a given Pod
func NewGenericWorkload(podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
workload, err := newGenericWorkload(podResource, dynamicClient, restMapper, objectCache)
if err != nil {
return workload, err
}
if len(workload.OriginalObjectJSON) == 0 {
return NewGenericWorkloadFromPod(podResource, podResource)
}
return workload, err
}
func newGenericWorkload(podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
workload, err := NewGenericWorkloadFromPod(podResource, nil)
if err != nil {
return workload, 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()
lastKey := ""
for len(owners) > 0 {
if len(owners) > 1 {
logrus.Warn("More than 1 owner found")
}
firstOwner := owners[0]
if firstOwner.Kind == "Node" {
break
}
workload.Kind = firstOwner.Kind
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, workload.ObjectMeta.GetNamespace(), firstOwner.Name)
lastKey = key
abstractObject, ok := objectCache[key]
if !ok {
err = cacheAllObjectsOfKind(firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
if err != nil {
logrus.Warnf("Error caching objects of Kind %s %v", firstOwner.Kind, err)
break
}
abstractObject, ok = objectCache[key]
if !ok {
logrus.Errorf("Cache missed %s again", key)
break
}
}
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
}
workload.ObjectMeta = objMeta
owners = abstractObject.GetOwnerReferences()
}
if lastKey != "" {
bytes, err := json.Marshal(objectCache[lastKey])
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 workload, nil
}
func cacheAllObjectsOfKind(apiVersion, kind string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
fqKind := schema.FromAPIVersionAndKind(apiVersion, kind)
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
if err != nil {
logrus.Warnf("Error retrieving mapping of API %s and Kind %s because of error: %v ", apiVersion, kind, err)
return err
}
objects, err := (*dynamicClient).Resource(mapping.Resource).Namespace("").List(kubeAPIMetaV1.ListOptions{})
if err != nil {
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v ", mapping.Resource.Version, mapping.Resource.Resource, err)
return err
}
for idx, object := range objects.Items {
key := fmt.Sprintf("%s/%s/%s", object.GetKind(), object.GetNamespace(), object.GetName())
objectCache[key] = objects.Items[idx]
}
return nil
}
func getObject(namespace, kind, version, name string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper) (*unstructured.Unstructured, error) {
fqKind := schema.ParseGroupKind(kind)
mapping, err := (*restMapper).RESTMapping(fqKind, version)
if err != nil {
return nil, err
}
object, err := (*dynamicClient).Resource(mapping.Resource).Namespace(namespace).Get(name, kubeAPIMetaV1.GetOptions{})
return object, err
}
// GetPodSpec looks inside arbitrary YAML for a PodSpec
func GetPodSpec(yaml map[string]interface{}) interface{} {
for _, child := range podSpecFields {
if childYaml, ok := yaml[child]; ok {
return GetPodSpec(childYaml.(map[string]interface{}))
}
}
if _, ok := yaml["containers"]; ok {
return yaml
}
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
}
+28 -341
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -15,357 +15,44 @@
package validator
import (
"fmt"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/pkg/validator/messages"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
// ContainerValidation tracks validation failures associated with a Container.
type ContainerValidation struct {
*ResourceValidation
Container *corev1.Container
IsInitContainer bool
parentPodSpec corev1.PodSpec
}
// ValidateContainer validates that each pod conforms to the Polaris config, returns a ResourceResult.
// FIXME When validating a container, there are some things in a container spec
// that can be affected by the podSpec. This means we need a copy of the
// relevant podSpec in order to check certain aspects of a containerSpec.
// Perhaps there is a more ideal solution instead of attaching a parent
// podSpec to every container Validation struct...
func ValidateContainer(container *corev1.Container, parentPodResult *PodResult, cnConf *conf.Configuration, isInit bool) ContainerResult {
cv := ContainerValidation{
Container: container,
ResourceValidation: &ResourceValidation{},
IsInitContainer: isInit,
// 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
}
// Support initializing
// FIXME This is a product of pulling in the podSpec, ideally we'd never
// expect this be nil but our tests have conditions in which the
// parent podResult isn't initialized in this ContainerValidation
// struct.
if parentPodResult == nil {
// initialize a blank pod spec
cv.parentPodSpec = corev1.PodSpec{}
} else {
cv.parentPodSpec = parentPodResult.podSpec
}
cv.validateResources(&cnConf.Resources)
cv.validateHealthChecks(&cnConf.HealthChecks)
cv.validateImage(&cnConf.Images)
cv.validateNetworking(&cnConf.Networking)
cv.validateSecurity(&cnConf.Security)
cRes := ContainerResult{
Name: container.Name,
Messages: cv.messages(),
Summary: cv.summary(),
Name: container.Name,
Results: results,
}
return cRes
return cRes, nil
}
func (cv *ContainerValidation) validateResources(resConf *conf.Resources) {
// Only validate resources for primary containers. Although it can
// be helpful to set these in certain cases, it usually isn't
if cv.IsInitContainer {
return
// 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)
}
category := messages.CategoryResources
res := cv.Container.Resources
if resConf.CPURequestsMissing.IsActionable() && res.Requests.Cpu().MilliValue() == 0 {
cv.addFailure(messages.CPURequestsFailure, resConf.CPURequestsMissing, category)
} else {
cv.validateResourceRange(messages.CPURequestsLabel, &resConf.CPURequestRanges, res.Requests.Cpu())
}
if resConf.CPULimitsMissing.IsActionable() && res.Limits.Cpu().MilliValue() == 0 {
cv.addFailure(messages.CPULimitsFailure, resConf.CPULimitsMissing, category)
} else {
cv.validateResourceRange(messages.CPULimitsLabel, &resConf.CPULimitRanges, res.Requests.Cpu())
}
if resConf.MemoryRequestsMissing.IsActionable() && res.Requests.Memory().MilliValue() == 0 {
cv.addFailure(messages.MemoryRequestsFailure, resConf.MemoryRequestsMissing, category)
} else {
cv.validateResourceRange(messages.MemoryRequestsLabel, &resConf.MemoryRequestRanges, res.Requests.Memory())
}
if resConf.MemoryLimitsMissing.IsActionable() && res.Limits.Memory().MilliValue() == 0 {
cv.addFailure(messages.MemoryLimitsFailure, resConf.MemoryLimitsMissing, category)
} else {
cv.validateResourceRange(messages.MemoryLimitsLabel, &resConf.MemoryLimitRanges, res.Limits.Memory())
for _, container := range pod.Containers {
result, err := ValidateContainer(conf, controller, &container, false)
if err != nil {
return nil, err
}
results = append(results, result)
}
}
func (cv *ContainerValidation) validateResourceRange(resourceName string, rangeConf *conf.ResourceRanges, res *resource.Quantity) {
warnAbove := rangeConf.Warning.Above
warnBelow := rangeConf.Warning.Below
errorAbove := rangeConf.Error.Above
errorBelow := rangeConf.Error.Below
category := messages.CategoryResources
if errorAbove != nil && errorAbove.MilliValue() < res.MilliValue() {
cv.addError(fmt.Sprintf(messages.ResourceAmountTooHighFailure, resourceName, errorAbove.String()), category)
} else if warnAbove != nil && warnAbove.MilliValue() < res.MilliValue() {
cv.addWarning(fmt.Sprintf(messages.ResourceAmountTooHighFailure, resourceName, warnAbove.String()), category)
} else if errorBelow != nil && errorBelow.MilliValue() > res.MilliValue() {
cv.addError(fmt.Sprintf(messages.ResourceAmountTooLowFailure, resourceName, errorBelow.String()), category)
} else if warnBelow != nil && warnBelow.MilliValue() > res.MilliValue() {
cv.addWarning(fmt.Sprintf(messages.ResourceAmountTooLowFailure, resourceName, warnBelow.String()), category)
} else {
if warnAbove != nil || warnBelow != nil || errorAbove != nil || errorBelow != nil {
cv.addSuccess(fmt.Sprintf(messages.ResourceAmountSuccess, resourceName), category)
} else {
cv.addSuccess(fmt.Sprintf(messages.ResourcePresentSuccess, resourceName), category)
}
}
}
func (cv *ContainerValidation) validateHealthChecks(conf *conf.HealthChecks) {
category := messages.CategoryHealthChecks
// Don't validate readiness probes on init containers
if !cv.IsInitContainer && conf.ReadinessProbeMissing.IsActionable() {
if cv.Container.ReadinessProbe == nil {
cv.addFailure(messages.ReadinessProbeFailure, conf.ReadinessProbeMissing, category)
} else {
cv.addSuccess(messages.ReadinessProbeSuccess, category)
}
}
if conf.LivenessProbeMissing.IsActionable() {
if cv.Container.LivenessProbe == nil {
cv.addFailure(messages.LivenessProbeFailure, conf.LivenessProbeMissing, category)
} else {
cv.addSuccess(messages.LivenessProbeSuccess, category)
}
}
}
func (cv *ContainerValidation) validateImage(imageConf *conf.Images) {
category := messages.CategoryImages
if imageConf.PullPolicyNotAlways.IsActionable() {
if cv.Container.ImagePullPolicy != corev1.PullAlways {
cv.addFailure(messages.ImagePullPolicyFailure, imageConf.PullPolicyNotAlways, category)
} else {
cv.addSuccess(messages.ImagePullPolicySuccess, category)
}
}
if imageConf.TagNotSpecified.IsActionable() {
img := strings.Split(cv.Container.Image, ":")
if len(img) == 1 || img[1] == "latest" {
cv.addFailure(messages.ImageTagFailure, imageConf.TagNotSpecified, category)
} else {
cv.addSuccess(messages.ImageTagSuccess, category)
}
}
}
func (cv *ContainerValidation) validateNetworking(networkConf *conf.Networking) {
category := messages.CategoryNetworking
if networkConf.HostPortSet.IsActionable() {
hostPortSet := false
for _, port := range cv.Container.Ports {
if port.HostPort != 0 {
hostPortSet = true
break
}
}
if hostPortSet {
cv.addFailure(messages.HostPortFailure, networkConf.HostPortSet, category)
} else {
cv.addSuccess(messages.HostPortSuccess, category)
}
}
}
func (cv *ContainerValidation) validateSecurity(securityConf *conf.Security) {
category := messages.CategorySecurity
securityContext := cv.Container.SecurityContext
podSecurityContext := cv.parentPodSpec.SecurityContext
// Support an empty container security context
if securityContext == nil {
securityContext = &corev1.SecurityContext{}
}
// Support an empty pod security context
if podSecurityContext == nil {
podSecurityContext = &corev1.PodSecurityContext{}
}
if securityConf.RunAsRootAllowed.IsActionable() {
if getBoolValue(securityContext.RunAsNonRoot) {
// Check if the container is explicitly set to True (pass)
cv.addSuccess(messages.RunAsRootSuccess, category)
} else if securityContext.RunAsNonRoot == nil {
// Check if the value in the container spec if nil (thus defaulting to the podspec)
// Check if the container value is not set
if getBoolValue(podSecurityContext.RunAsNonRoot) {
// if the pod spec default for containers is true, then pass
cv.addSuccess(messages.RunAsRootSuccess, category)
} else {
// else fail as RunAsNonRoot defaults to false
cv.addFailure(messages.RunAsRootFailure, securityConf.RunAsRootAllowed, category)
}
} else {
cv.addFailure(messages.RunAsRootFailure, securityConf.RunAsRootAllowed, category)
}
}
if securityConf.RunAsPrivileged.IsActionable() {
if getBoolValue(securityContext.Privileged) {
cv.addFailure(messages.RunAsPrivilegedFailure, securityConf.RunAsPrivileged, category)
} else {
cv.addSuccess(messages.RunAsPrivilegedSuccess, category)
}
}
if securityConf.NotReadOnlyRootFileSystem.IsActionable() {
if getBoolValue(securityContext.ReadOnlyRootFilesystem) {
cv.addSuccess(messages.ReadOnlyFilesystemSuccess, category)
} else {
cv.addFailure(messages.ReadOnlyFilesystemFailure, securityConf.NotReadOnlyRootFileSystem, category)
}
}
if securityConf.PrivilegeEscalationAllowed.IsActionable() {
if getBoolValue(securityContext.AllowPrivilegeEscalation) {
cv.addFailure(messages.PrivilegeEscalationFailure, securityConf.PrivilegeEscalationAllowed, category)
} else {
cv.addSuccess(messages.PrivilegeEscalationSuccess, category)
}
}
hasSecurityError :=
!cv.validateCapabilities(securityConf.Capabilities.Error, conf.SeverityError)
hasSecurityWarning :=
!cv.validateCapabilities(securityConf.Capabilities.Warning, conf.SeverityWarning)
hasSecurityCheck := func(confLists conf.SecurityCapabilityLists) bool {
return len(confLists.IfAnyAdded) > 0 ||
len(confLists.IfAnyAddedBeyond) > 0 ||
len(confLists.IfAnyNotDropped) > 0
}
if !hasSecurityError && !hasSecurityWarning &&
(hasSecurityCheck(securityConf.Capabilities.Error) ||
hasSecurityCheck(securityConf.Capabilities.Warning)) {
cv.addSuccess(messages.SecurityCapabilitiesSuccess, category)
}
}
func (cv *ContainerValidation) validateCapabilities(confLists conf.SecurityCapabilityLists, severity conf.Severity) bool {
category := messages.CategorySecurity
capabilities := &corev1.Capabilities{}
if cv.Container.SecurityContext != nil && cv.Container.SecurityContext.Capabilities != nil {
capabilities = cv.Container.SecurityContext.Capabilities
}
everythingOK := true
if len(confLists.IfAnyAdded) > 0 {
intersectAdds := capIntersection(capabilities.Add, confLists.IfAnyAdded)
if len(intersectAdds) > 0 {
capsString := commaSeparatedCapabilities(intersectAdds)
cv.addFailure(fmt.Sprintf(messages.SecurityCapabilitiesAddedFailure, capsString), severity, category)
everythingOK = false
} else if capContains(capabilities.Add, "ALL") {
cv.addFailure(fmt.Sprintf(messages.SecurityCapabilitiesAddedFailure, "ALL"), severity, category)
everythingOK = false
}
}
if len(confLists.IfAnyAddedBeyond) > 0 {
differentAdds := capDifference(capabilities.Add, confLists.IfAnyAddedBeyond)
if len(differentAdds) > 0 {
capsString := commaSeparatedCapabilities(differentAdds)
cv.addFailure(fmt.Sprintf(messages.SecurityCapabilitiesAddedFailure, capsString), severity, category)
everythingOK = false
} else if capContains(capabilities.Add, "ALL") {
cv.addFailure(fmt.Sprintf(messages.SecurityCapabilitiesAddedFailure, "ALL"), severity, category)
everythingOK = false
}
}
if len(confLists.IfAnyNotDropped) > 0 {
missingDrops := capDifference(confLists.IfAnyNotDropped, capabilities.Drop)
if len(missingDrops) > 0 && !capContains(capabilities.Drop, "ALL") {
capsString := commaSeparatedCapabilities(missingDrops)
cv.addFailure(fmt.Sprintf(messages.SecurityCapabilitiesNotDroppedFailure, capsString), severity, category)
everythingOK = false
}
}
return everythingOK
}
func commaSeparatedCapabilities(caps []corev1.Capability) string {
capsString := ""
for _, cap := range caps {
capsString = fmt.Sprintf("%s, %s", capsString, cap)
}
return capsString[2:]
}
func capIntersection(a, b []corev1.Capability) []corev1.Capability {
result := []corev1.Capability{}
hash := map[corev1.Capability]bool{}
for _, s := range a {
hash[s] = true
}
for _, s := range b {
if hash[s] {
result = append(result, s)
}
}
return result
}
func capDifference(b, a []corev1.Capability) []corev1.Capability {
result := []corev1.Capability{}
hash := map[corev1.Capability]bool{}
for _, s := range a {
hash[s] = true
}
for _, s := range b {
if !hash[s] {
result = append(result, s)
}
}
return result
}
func capContains(list []corev1.Capability, val corev1.Capability) bool {
for _, s := range list {
if s == val {
return true
}
}
return false
}
// getBoolValue returns false if nil or returns the value of the bool pointer
func getBoolValue(val *bool) bool {
if val == nil {
return false
}
return *val
return results, nil
}
File diff suppressed because it is too large Load Diff
+46 -66
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -15,82 +15,62 @@
package validator
import (
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/pkg/kube"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"strings"
"github.com/sirupsen/logrus"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
)
// ControllerSpec is a generic type for k8s controller specs
type ControllerSpec struct {
Template corev1.PodTemplateSpec
}
// Controller is a generic type for k8s controllers (e.g. Deployments and StatefulSets)
type Controller struct {
Type string
Name string
Namespace string
Spec ControllerSpec
}
const exemptionAnnotationKey = "polaris.fairwinds.com/exempt"
// ValidateController validates a single controller, returns a ControllerResult.
func ValidateController(conf conf.Configuration, controller Controller) ControllerResult {
pod := controller.Spec.Template.Spec
podResult := ValidatePod(conf, &pod)
return ControllerResult{
Type: controller.Type,
Name: controller.Name,
func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (ControllerResult, error) {
podResult, err := ValidatePod(conf, controller)
if err != nil {
return ControllerResult{}, err
}
controllerResult, err := applyControllerSchemaChecks(conf, controller)
if err != nil {
return ControllerResult{}, err
}
result := ControllerResult{
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,
// returns a list of ResourceResults organized by namespace.
func ValidateControllers(config conf.Configuration, kubeResources *kube.ResourceProvider, nsResults *NamespacedResults) {
controllers := []Controller{}
for _, deploy := range kubeResources.Deployments {
controllers = append(controllers, ControllerFromDeployment(deploy))
}
for _, deploy := range kubeResources.StatefulSets {
controllers = append(controllers, ControllerFromStatefulSet(deploy))
}
for _, controller := range controllers {
controllerResult := ValidateController(config, controller)
nsResult := nsResults.getNamespaceResult(controller.Namespace)
nsResult.Summary.appendResults(*controllerResult.PodResult.Summary)
if controller.Type == "Deployment" {
nsResult.DeploymentResults = append(nsResult.DeploymentResults, controllerResult)
} else if controller.Type == "StatefulSet" {
nsResult.StatefulSetResults = append(nsResult.StatefulSetResults, controllerResult)
// builds a list of ResourceResults organized by namespace.
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]ControllerResult, error) {
controllersToAudit := kubeResources.Controllers
results := []ControllerResult{}
for _, controller := range controllersToAudit {
if !config.DisallowExemptions && hasExemptionAnnotation(controller) {
continue
}
result, err := ValidateController(config, controller)
if err != nil {
logrus.Warn("An error occured validating controller:", err)
return nil, err
}
results = append(results, result)
}
return results, nil
}
// ControllerFrom* functions are 100% boilerplate
// ControllerFromDeployment creates a controller
func ControllerFromDeployment(c appsv1.Deployment) Controller {
spec := ControllerSpec{
Template: c.Spec.Template,
}
return Controller{
Type: "Deployment",
Name: c.Name,
Namespace: c.Namespace,
Spec: spec,
}
}
// ControllerFromStatefulSet creates a controller
func ControllerFromStatefulSet(c appsv1.StatefulSet) Controller {
spec := ControllerSpec{
Template: c.Spec.Template,
}
return Controller{
Type: "StatefulSet",
Name: c.Name,
Namespace: c.Namespace,
Spec: spec,
}
func hasExemptionAnnotation(ctrl kube.GenericWorkload) bool {
annot := ctrl.ObjectMeta.GetAnnotations()
val := annot[exemptionAnnotationKey]
return strings.ToLower(val) == "true"
}
+202
View File
@@ -0,0 +1,202 @@
// 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"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/test"
)
func TestValidateController(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"hostIPCSet": conf.SeverityDanger,
"hostPIDSet": conf.SeverityDanger,
},
}
deployment, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
assert.NoError(t, err)
deployment.Kind = "Deployment"
expectedSum := CountSummary{
Successes: uint(2),
Warnings: uint(0),
Dangers: uint(0),
}
expectedResults := ResultSet{
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Success: true, Severity: "danger", Category: "Security"},
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualResult, err := ValidateController(&c, deployment)
if err != nil {
panic(err)
}
assert.Equal(t, "Deployment", actualResult.Kind)
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
assert.EqualValues(t, expectedResults, actualResult.PodResult.Results)
}
func TestControllerLevelChecks(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"multipleReplicasForDeployment": conf.SeverityDanger,
},
}
resources, err := kube.CreateResourceProviderFromPath("../kube/test_files/test_1")
assert.Equal(t, nil, err, "Error should be nil")
assert.Equal(t, 8, len(resources.Controllers), "Should have eight controllers")
expectedSum := CountSummary{
Successes: uint(0),
Warnings: uint(0),
Dangers: uint(1),
}
expectedResults := ResultSet{
"multipleReplicasForDeployment": {ID: "multipleReplicasForDeployment", Message: "Only one replica is scheduled", Success: false, Severity: "danger", Category: "Reliability"},
}
for _, controller := range resources.Controllers {
if controller.Kind == "Deployment" && controller.ObjectMeta.GetName() == "test-deployment" {
actualResult, err := ValidateController(&c, controller)
if err != nil {
panic(err)
}
assert.Equal(t, "Deployment", actualResult.Kind)
assert.Equal(t, 1, len(actualResult.Results), "should be equal")
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
assert.EqualValues(t, expectedResults, actualResult.Results)
}
}
}
func TestSkipHealthChecks(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"readinessProbeMissing": conf.SeverityDanger,
"livenessProbeMissing": conf.SeverityWarning,
},
}
pod := test.MockPod()
pod.Spec.InitContainers = []corev1.Container{test.MockContainer("test")}
deployment, err := kube.NewGenericWorkloadFromPod(pod, nil)
assert.NoError(t, err)
deployment.Kind = "Deployment"
expectedSum := CountSummary{
Successes: uint(0),
Warnings: uint(1),
Dangers: uint(1),
}
expectedResults := ResultSet{
"readinessProbeMissing": {ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Success: false, Severity: "danger", Category: "Health Checks"},
"livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Health Checks"},
}
actualResult, err := ValidateController(&c, deployment)
if err != nil {
panic(err)
}
assert.Equal(t, "Deployment", actualResult.Kind)
assert.Equal(t, 2, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
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)
assert.NoError(t, err)
job.Kind = "Job"
expectedSum = CountSummary{
Successes: uint(0),
Warnings: uint(0),
Dangers: uint(0),
}
expectedResults = ResultSet{}
actualResult, err = ValidateController(&c, job)
if err != nil {
panic(err)
}
assert.Equal(t, "Job", actualResult.Kind)
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[0].Results)
cronjob, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil)
assert.NoError(t, err)
cronjob.Kind = "CronJob"
expectedSum = CountSummary{
Successes: uint(0),
Warnings: uint(0),
Dangers: uint(0),
}
expectedResults = ResultSet{}
actualResult, err = ValidateController(&c, cronjob)
if err != nil {
panic(err)
}
assert.Equal(t, "CronJob", actualResult.Kind)
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[0].Results)
}
func TestControllerExemptions(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"readinessProbeMissing": conf.SeverityDanger,
"livenessProbeMissing": conf.SeverityWarning,
},
}
pod := test.MockPod()
workload, err := kube.NewGenericWorkloadFromPod(pod, nil)
assert.NoError(t, err)
workload.Kind = "Deployment"
resources := &kube.ResourceProvider{
Controllers: []kube.GenericWorkload{workload},
}
expectedSum := CountSummary{
Successes: uint(0),
Warnings: uint(1),
Dangers: uint(1),
}
actualResults, err := ValidateControllers(&c, resources)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualResults))
assert.Equal(t, "Deployment", actualResults[0].Kind)
assert.EqualValues(t, expectedSum, actualResults[0].GetSummary())
resources.Controllers[0].ObjectMeta.SetAnnotations(map[string]string{
exemptionAnnotationKey: "true",
})
actualResults, err = ValidateControllers(&c, resources)
if err != nil {
panic(err)
}
assert.Equal(t, 0, len(actualResults))
}
+53 -55
View File
@@ -1,79 +1,77 @@
package validator
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"time"
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/pkg/kube"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/sirupsen/logrus"
apiMachineryYAML "k8s.io/apimachinery/pkg/util/yaml"
)
const (
// PolarisOutputVersion is the version of the current output structure
PolarisOutputVersion = "0.2"
)
// ClusterSummary contains Polaris results as well as some high-level stats
type ClusterSummary struct {
Results ResultSummary
Version string
Nodes int
Pods int
Namespaces int
Deployments int
StatefulSets int
Score uint
}
// AuditData contains all the data from a full Polaris audit
type AuditData struct {
PolarisOutputVersion string
AuditTime string
SourceType string
SourceName string
DisplayName string
ClusterSummary ClusterSummary
NamespacedResults NamespacedResults
}
// RunAudit runs a full Polaris audit and returns an AuditData object
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
nsResults := NamespacedResults{}
ValidateControllers(config, kubeResources, &nsResults)
clusterResults := ResultSummary{}
// Aggregate all summary counts to get a clusterwide count.
for _, nsRes := range nsResults {
for _, dr := range nsRes.DeploymentResults {
clusterResults.appendResults(*dr.PodResult.Summary)
}
for _, dr := range nsRes.StatefulSetResults {
clusterResults.appendResults(*dr.PodResult.Summary)
}
}
displayName := config.DisplayName
if displayName == "" {
displayName = kubeResources.SourceName
}
results, err := ValidateControllers(&config, kubeResources)
if err != nil {
return AuditData{}, err
}
auditData := AuditData{
PolarisOutputVersion: PolarisOutputVersion,
AuditTime: kubeResources.CreationTime.Format(time.RFC3339),
SourceType: kubeResources.SourceType,
SourceName: kubeResources.SourceName,
DisplayName: displayName,
ClusterSummary: ClusterSummary{
Version: kubeResources.ServerVersion,
Nodes: len(kubeResources.Nodes),
Pods: len(kubeResources.Pods),
Namespaces: len(kubeResources.Namespaces),
Deployments: len(kubeResources.Deployments),
StatefulSets: len(kubeResources.StatefulSets),
Results: clusterResults,
Score: clusterResults.Totals.GetScore(),
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: len(results),
},
NamespacedResults: nsResults,
Results: results,
}
return auditData, nil
}
// ReadAuditFromFile reads the data from a past audit stored in a JSON or YAML file.
func ReadAuditFromFile(fileName string) AuditData {
auditData := AuditData{}
oldFileBytes, err := ioutil.ReadFile(fileName)
if err != nil {
logrus.Errorf("Unable to read contents of loaded file: %v", err)
os.Exit(1)
}
auditData, err = ParseAudit(oldFileBytes)
if err != nil {
logrus.Errorf("Error parsing file contents into auditData: %v", err)
os.Exit(1)
}
return auditData
}
// ParseAudit decodes either a YAML or JSON file and returns AuditData.
func ParseAudit(oldFileBytes []byte) (AuditData, error) {
reader := bytes.NewReader(oldFileBytes)
conf := AuditData{}
d := apiMachineryYAML.NewYAMLOrJSONDecoder(reader, 4096)
for {
if err := d.Decode(&conf); err != nil {
if err == io.EOF {
return conf, nil
}
return conf, fmt.Errorf("Decoding config failed: %v", err)
}
}
}
+27 -33
View File
@@ -3,58 +3,52 @@ package validator
import (
"testing"
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/pkg/kube"
"github.com/reactiveops/polaris/test"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"
)
func TestGetTemplateData(t *testing.T) {
k8s := test.SetupTestAPI()
k8s, dynamicClient := test.SetupTestAPI()
k8s = test.SetupAddControllers(k8s, "test")
resources, err := kube.CreateResourceProviderFromAPI(k8s, "test")
k8s = test.SetupAddExtraControllerVersions(k8s, "test-extra")
// TODO figure out how to mock out dynamic client.
// and add in pods for all controllers to fill out tests.
resources, err := kube.CreateResourceProviderFromAPI(k8s, "test", &dynamicClient)
assert.Equal(t, err, nil, "error should be nil")
c := conf.Configuration{
HealthChecks: conf.HealthChecks{
ReadinessProbeMissing: conf.SeverityError,
LivenessProbeMissing: conf.SeverityWarning,
Checks: map[string]conf.Severity{
"readinessProbeMissing": conf.SeverityDanger,
"livenessProbeMissing": conf.SeverityWarning,
},
}
sum := ResultSummary{
Totals: CountSummary{
Successes: uint(8),
Warnings: uint(2),
Errors: uint(2),
},
ByCategory: CategorySummary{},
}
sum.ByCategory["Health Checks"] = &CountSummary{
sum := CountSummary{
Successes: uint(0),
Warnings: uint(2),
Errors: uint(2),
}
sum.ByCategory["Resources"] = &CountSummary{
Successes: uint(8),
Warnings: uint(0),
Errors: uint(0),
Warnings: uint(1),
Dangers: uint(1),
}
actualAudit, err := RunAudit(c, resources)
assert.Equal(t, err, nil, "error should be nil")
assert.EqualValues(t, sum, actualAudit.ClusterSummary.Results)
assert.EqualValues(t, sum, actualAudit.GetSummary())
assert.Equal(t, actualAudit.SourceType, "Cluster", "should be from a cluster")
assert.Equal(t, actualAudit.SourceName, "test", "should be from a cluster")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults), "should be equal")
assert.Equal(t, 6, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
expected := []struct {
kind string
results int
}{
{kind: "Pod", results: 2},
}
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults), "should be equal")
assert.Equal(t, 6, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
assert.Equal(t, len(expected), len(actualAudit.Results))
for idx, result := range actualAudit.Results {
assert.Equal(t, expected[idx].kind, result.Kind)
assert.Equal(t, 1, len(result.PodResult.ContainerResults))
assert.Equal(t, expected[idx].results, len(result.PodResult.ContainerResults[0].Results))
}
}
-99
View File
@@ -1,99 +0,0 @@
package messages
const (
// CategoryHealthChecks category
CategoryHealthChecks = "Health Checks"
// CategorySecurity category
CategorySecurity = "Security"
// CategoryNetworking category
CategoryNetworking = "Networking"
// CategoryResources category
CategoryResources = "Resources"
// CategoryImages category
CategoryImages = "Images"
// CPURequestsLabel label
CPURequestsLabel = "CPU requests"
// CPULimitsLabel label
CPULimitsLabel = "CPU limits"
// MemoryRequestsLabel label
MemoryRequestsLabel = "Memory requests"
// MemoryLimitsLabel label
MemoryLimitsLabel = "Memory limits"
// CPURequestsFailure message
CPURequestsFailure = "CPU requests should be set"
// CPULimitsFailure message
CPULimitsFailure = "CPU limits should be set"
// MemoryRequestsFailure message
MemoryRequestsFailure = "Memory requests should be set"
// MemoryLimitsFailure message
MemoryLimitsFailure = "Memory limits should be set"
// ResourceAmountTooHighFailure message
ResourceAmountTooHighFailure = "%s should be lower than %s"
// ResourceAmountTooLowFailure message
ResourceAmountTooLowFailure = "%s should be higher than %s"
// ResourceAmountSuccess message
ResourceAmountSuccess = "%s are within the expected range"
// ResourcePresentSuccess message
ResourcePresentSuccess = "%s are set"
// ReadinessProbeFailure message
ReadinessProbeFailure = "Readiness probe should be configured"
// ReadinessProbeSuccess message
ReadinessProbeSuccess = "Readiness probe configured"
// LivenessProbeFailure message
LivenessProbeFailure = "Liveness probe should be configured"
// LivenessProbeSuccess message
LivenessProbeSuccess = "Liveness probe is configured"
// ImageTagFailure message
ImageTagFailure = "Image tag should be specified"
// ImageTagSuccess message
ImageTagSuccess = "Image tag is specified"
// ImagePullPolicyFailure message
ImagePullPolicyFailure = "Image pull policy should be \"Always\""
// ImagePullPolicySuccess message
ImagePullPolicySuccess = "Image pull policy is \"Always\""
// HostPortFailure message
HostPortFailure = "Host port should not be configured"
// HostPortSuccess message
HostPortSuccess = "Host port is not configured"
// RunAsRootFailure message
RunAsRootFailure = "Should not be allowed to run as root"
// RunAsRootSuccess message
RunAsRootSuccess = "Is not allowed to run as root"
// RunAsPrivilegedFailure message
RunAsPrivilegedFailure = "Should not be running as privileged"
// RunAsPrivilegedSuccess message
RunAsPrivilegedSuccess = "Not running as privileged"
// ReadOnlyFilesystemSuccess message
ReadOnlyFilesystemSuccess = "Filesystem is read only"
// ReadOnlyFilesystemFailure message
ReadOnlyFilesystemFailure = "Filesystem should be read only"
// PrivilegeEscalationFailure message
PrivilegeEscalationFailure = "Privilege escalation should not be allowed"
// PrivilegeEscalationSuccess message
PrivilegeEscalationSuccess = "Privilege escalation not allowed"
// SecurityCapabilitiesAddedFailure message
SecurityCapabilitiesAddedFailure = "The following security capabilities should not be added: %v"
// SecurityCapabilitiesNotDroppedFailure message
SecurityCapabilitiesNotDroppedFailure = "The following security capabilities should be dropped: %v"
// SecurityCapabilitiesSuccess message
SecurityCapabilitiesSuccess = "Security capabilities are within the configured limits"
// HostAliasFailure message
HostAliasFailure = "Host alias should not be configured"
// HostAliasSuccess message
HostAliasSuccess = "Host alias is not configured"
// HostIPCFailure message
HostIPCFailure = "Host IPC should not be configured"
// HostIPCSuccess message
HostIPCSuccess = "Host IPC is not configured"
// HostPIDFailure message
HostPIDFailure = "Host PID should not be configured"
// HostPIDSuccess message
HostPIDSuccess = "Host PID is not configured"
// HostNetworkFailure message
HostNetworkFailure = "Host network should not be configured"
// HostNetworkSuccess message
HostNetworkSuccess = "Host network is not configured"
)
+81
View File
@@ -0,0 +1,81 @@
// 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 (
"time"
"github.com/fairwindsops/polaris/pkg/config"
)
const (
// PolarisOutputVersion is the version of the current output structure
PolarisOutputVersion = "1.0"
)
// AuditData contains all the data from a full Polaris audit
type AuditData struct {
PolarisOutputVersion string
AuditTime string
SourceType string
SourceName string
DisplayName string
ClusterInfo ClusterInfo
Results []ControllerResult
}
// ClusterInfo contains Polaris results as well as some high-level stats
type ClusterInfo struct {
Version string
Nodes int
Pods int
Namespaces int
Controllers int
}
// ResultMessage is the result of a given check
type ResultMessage struct {
ID string
Message string
Success bool
Severity config.Severity
Category string
}
// ResultSet contiains the results for a set of checks
type ResultSet map[string]ResultMessage
// ControllerResult provides results for a controller
type ControllerResult struct {
Name string
Namespace string
Kind string
Results ResultSet
PodResult PodResult
CreatedTime time.Time
}
// PodResult provides a list of validation messages for each pod.
type PodResult struct {
Name string
Results ResultSet
ContainerResults []ContainerResult
}
// ContainerResult provides a list of validation messages for each container.
type ContainerResult struct {
Name string
Results ResultSet
}
+12 -67
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -15,79 +15,24 @@
package validator
import (
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/pkg/validator/messages"
corev1 "k8s.io/api/core/v1"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
)
// PodValidation tracks validation failures associated with a Pod.
type PodValidation struct {
*ResourceValidation
Pod *corev1.PodSpec
}
// ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult.
func ValidatePod(podConf conf.Configuration, pod *corev1.PodSpec) PodResult {
pv := PodValidation{
Pod: pod,
ResourceValidation: &ResourceValidation{},
func ValidatePod(conf *config.Configuration, controller kube.GenericWorkload) (PodResult, error) {
podResults, err := applyPodSchemaChecks(conf, controller)
if err != nil {
return PodResult{}, err
}
pv.validateSecurity(&podConf.Security)
pv.validateNetworking(&podConf.Networking)
pRes := PodResult{
Messages: pv.messages(),
Results: podResults,
ContainerResults: []ContainerResult{},
Summary: pv.summary(),
podSpec: *pod,
}
pv.validateContainers(pod.InitContainers, &pRes, &podConf, true)
pv.validateContainers(pod.Containers, &pRes, &podConf, false)
for _, cRes := range pRes.ContainerResults {
pRes.Summary.appendResults(*cRes.Summary)
}
return pRes
}
func (pv *PodValidation) validateContainers(containers []corev1.Container, pRes *PodResult, podConf *conf.Configuration, isInit bool) {
for _, container := range containers {
cRes := ValidateContainer(&container, pRes, podConf, isInit)
pRes.ContainerResults = append(pRes.ContainerResults, cRes)
}
}
func (pv *PodValidation) validateSecurity(securityConf *conf.Security) {
category := messages.CategorySecurity
if securityConf.HostIPCSet.IsActionable() {
if pv.Pod.HostIPC {
pv.addFailure(messages.HostIPCFailure, securityConf.HostIPCSet, category)
} else {
pv.addSuccess(messages.HostIPCSuccess, category)
}
}
if securityConf.HostPIDSet.IsActionable() {
if pv.Pod.HostPID {
pv.addFailure(messages.HostPIDFailure, securityConf.HostPIDSet, category)
} else {
pv.addSuccess(messages.HostPIDSuccess, category)
}
}
}
func (pv *PodValidation) validateNetworking(networkConf *conf.Networking) {
category := messages.CategoryNetworking
if networkConf.HostNetworkSet.IsActionable() {
if pv.Pod.HostNetwork {
pv.addFailure(messages.HostNetworkFailure, networkConf.HostNetworkSet, category)
} else {
pv.addSuccess(messages.HostNetworkSuccess, category)
}
pRes.ContainerResults, err = ValidateAllContainers(conf, controller)
if err != nil {
return pRes, err
}
return pRes, nil
}
+186 -41
View File
@@ -1,4 +1,4 @@
// Copyright 2019 ReactiveOps
// 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.
@@ -17,60 +17,205 @@ package validator
import (
"testing"
conf "github.com/reactiveops/polaris/pkg/config"
"github.com/reactiveops/polaris/test"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/test"
)
func TestValidatePod(t *testing.T) {
c := conf.Configuration{
Security: conf.Security{
HostIPCSet: conf.SeverityError,
HostPIDSet: conf.SeverityError,
},
Networking: conf.Networking{
HostNetworkSet: conf.SeverityWarning,
HostPortSet: conf.SeverityError,
Checks: map[string]conf.Severity{
"hostIPCSet": conf.SeverityDanger,
"hostPIDSet": conf.SeverityDanger,
"hostNetworkSet": conf.SeverityWarning,
"hostPortSet": conf.SeverityDanger,
},
}
k8s := test.SetupTestAPI()
k8s, _ := test.SetupTestAPI()
k8s = test.SetupAddControllers(k8s, "test")
pod := test.MockPod()
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(8),
Warnings: uint(0),
Errors: uint(0),
},
ByCategory: make(map[string]*CountSummary),
}
expectedSum.ByCategory["Networking"] = &CountSummary{
Successes: uint(2),
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Resources"] = &CountSummary{
p := test.MockPod()
deployment, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(4),
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Security"] = &CountSummary{
Successes: uint(2),
Warnings: uint(0),
Errors: uint(0),
Dangers: uint(0),
}
expectedMessages := []*ResultMessage{
{Message: "Host IPC is not configured", Type: "success", Category: "Security"},
{Message: "Host PID is not configured", Type: "success", Category: "Security"},
{Message: "Host network is not configured", Type: "success", Category: "Networking"},
expectedResults := ResultSet{
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Success: true, Severity: "danger", Category: "Security"},
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Networking"},
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult := ValidatePod(c, &pod.Spec)
actualPodResult, err := ValidatePod(&c, deployment)
if err != nil {
panic(err)
}
assert.Equal(t, len(actualPodResult.ContainerResults), 1, "should be equal")
assert.EqualValues(t, actualPodResult.Summary, &expectedSum)
assert.EqualValues(t, actualPodResult.Messages, expectedMessages)
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestInvalidIPCPod(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"hostIPCSet": conf.SeverityDanger,
"hostPIDSet": conf.SeverityDanger,
"hostNetworkSet": conf.SeverityWarning,
"hostPortSet": conf.SeverityDanger,
},
}
k8s, _ := test.SetupTestAPI()
k8s = test.SetupAddControllers(k8s, "test")
p := test.MockPod()
p.Spec.HostIPC = true
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
Warnings: uint(0),
Dangers: uint(1),
}
expectedResults := ResultSet{
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC should not be configured", Success: false, Severity: "danger", Category: "Security"},
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Networking"},
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestInvalidNeworkPod(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"hostNetworkSet": conf.SeverityWarning,
"hostPortSet": conf.SeverityDanger,
"hostIPCSet": conf.SeverityDanger,
"hostPIDSet": conf.SeverityDanger,
},
}
k8s, _ := test.SetupTestAPI()
k8s = test.SetupAddControllers(k8s, "test")
p := test.MockPod()
p.Spec.HostNetwork = true
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
Warnings: uint(1),
Dangers: uint(0),
}
expectedResults := ResultSet{
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network should not be configured", Success: false, Severity: "warning", Category: "Networking"},
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Success: true, Severity: "danger", Category: "Security"},
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestInvalidPIDPod(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"hostIPCSet": conf.SeverityDanger,
"hostPIDSet": conf.SeverityDanger,
"hostNetworkSet": conf.SeverityWarning,
"hostPortSet": conf.SeverityDanger,
},
}
k8s, _ := test.SetupTestAPI()
k8s = test.SetupAddControllers(k8s, "test")
p := test.MockPod()
p.Spec.HostPID = true
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
Warnings: uint(0),
Dangers: uint(1),
}
expectedResults := ResultSet{
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID should not be configured", Success: false, Severity: "danger", Category: "Security"},
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Success: true, Severity: "danger", Category: "Security"},
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Networking"},
}
actualPodResult, err := ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
func TestExemption(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"hostIPCSet": conf.SeverityDanger,
"hostNetworkSet": conf.SeverityWarning,
"hostPIDSet": conf.SeverityDanger,
"hostPortSet": conf.SeverityDanger,
},
Exemptions: []conf.Exemption{
conf.Exemption{
Rules: []string{"hostIPCSet"},
ControllerNames: []string{"foo"},
},
},
}
k8s, _ := test.SetupTestAPI()
k8s = test.SetupAddControllers(k8s, "test")
p := test.MockPod()
p.Spec.HostIPC = true
p.ObjectMeta = metav1.ObjectMeta{
Name: "foo",
}
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
assert.NoError(t, err)
expectedSum := CountSummary{
Successes: uint(3),
Warnings: uint(0),
Dangers: uint(0),
}
expectedResults := ResultSet{
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Networking"},
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}
actualPodResult, err := ValidatePod(&c, workload)
if err != nil {
panic(err)
}
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
assert.EqualValues(t, expectedResults, actualPodResult.Results)
}
-106
View File
@@ -1,106 +0,0 @@
// Copyright 2019 ReactiveOps
//
// 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/reactiveops/polaris/pkg/config"
"github.com/sirupsen/logrus"
)
// ResourceValidation contains methods shared by PodValidation and ContainerValidation
type ResourceValidation struct {
Errors []*ResultMessage
Warnings []*ResultMessage
Successes []*ResultMessage
}
func (rv *ResourceValidation) messages() []*ResultMessage {
messages := []*ResultMessage{}
messages = append(messages, rv.Errors...)
messages = append(messages, rv.Warnings...)
messages = append(messages, rv.Successes...)
return messages
}
func (rv *ResourceValidation) summary() *ResultSummary {
counts := CountSummary{
Errors: uint(len(rv.Errors)),
Warnings: uint(len(rv.Warnings)),
Successes: uint(len(rv.Successes)),
}
byCategory := CategorySummary{}
for _, msg := range rv.messages() {
if _, ok := byCategory[msg.Category]; !ok {
byCategory[msg.Category] = &CountSummary{}
}
if msg.Type == MessageTypeError {
byCategory[msg.Category].Errors++
} else if msg.Type == MessageTypeWarning {
byCategory[msg.Category].Warnings++
} else if msg.Type == MessageTypeSuccess {
byCategory[msg.Category].Successes++
}
}
return &ResultSummary{
Totals: counts,
ByCategory: byCategory,
}
}
func (rv *ResourceValidation) addMessage(message ResultMessage) {
if message.Type == MessageTypeError {
rv.Errors = append(rv.Errors, &message)
} else if message.Type == MessageTypeWarning {
rv.Warnings = append(rv.Warnings, &message)
} else if message.Type == MessageTypeSuccess {
rv.Successes = append(rv.Successes, &message)
} else {
panic("Bad message type")
}
}
func (rv *ResourceValidation) addFailure(message string, severity conf.Severity, category string) {
if severity == conf.SeverityError {
rv.addError(message, category)
} else if severity == conf.SeverityWarning {
rv.addWarning(message, category)
} else {
logrus.Errorf("Invalid severity: %s", severity)
}
}
func (rv *ResourceValidation) addError(message string, category string) {
rv.Errors = append(rv.Errors, &ResultMessage{
Message: message,
Type: MessageTypeError,
Category: category,
})
}
func (rv *ResourceValidation) addWarning(message string, category string) {
rv.Warnings = append(rv.Warnings, &ResultMessage{
Message: message,
Type: MessageTypeWarning,
Category: category,
})
}
func (rv *ResourceValidation) addSuccess(message string, category string) {
rv.Successes = append(rv.Successes, &ResultMessage{
Message: message,
Type: MessageTypeSuccess,
Category: category,
})
}
+205
View File
@@ -0,0 +1,205 @@
package validator
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
packr "github.com/gobuffalo/packr/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
)
var (
schemaBox = (*packr.Box)(nil)
builtInChecks = map[string]config.SchemaCheck{}
// We explicitly set the order to avoid thrash in the
// tests as we migrate toward JSON schema
checkOrder = []string{
// Controller Checks
"multipleReplicasForDeployment",
// Pod checks
"hostIPCSet",
"hostPIDSet",
"hostNetworkSet",
// Container checks
"memoryLimitsMissing",
"memoryRequestsMissing",
"cpuLimitsMissing",
"cpuRequestsMissing",
"readinessProbeMissing",
"livenessProbeMissing",
"pullPolicyNotAlways",
"tagNotSpecified",
"hostPortSet",
"runAsRootAllowed",
"runAsPrivileged",
"notReadOnlyRootFilesystem",
"privilegeEscalationAllowed",
"dangerousCapabilities",
"insecureCapabilities",
"priorityClassNotSet",
}
)
func init() {
schemaBox = packr.New("Schemas", "../../checks")
for _, checkID := range checkOrder {
contents, err := schemaBox.Find(checkID + ".yaml")
if err != nil {
panic(err)
}
check, err := parseCheck(contents)
if err != nil {
panic(err)
}
check.ID = checkID
builtInChecks[checkID] = check
}
}
func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
reader := bytes.NewReader(rawBytes)
check := config.SchemaCheck{}
d := yaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
if err := d.Decode(&check); err != nil {
if err == io.EOF {
return check, nil
}
return check, fmt.Errorf("Decoding schema check failed: %v", err)
}
}
}
func resolveCheck(conf *config.Configuration, checkID string, controller kube.GenericWorkload, target config.TargetKind, isInitContainer bool) (*config.SchemaCheck, error) {
check, ok := conf.CustomChecks[checkID]
if !ok {
check, ok = builtInChecks[checkID]
}
if !ok {
return nil, fmt.Errorf("Check %s not found", checkID)
}
if !conf.IsActionable(check.ID, controller.ObjectMeta.GetName()) {
return nil, nil
}
if !check.IsActionable(target, controller.Kind, isInitContainer) {
return nil, nil
}
return &check, nil
}
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool) ResultMessage {
result := ResultMessage{
ID: check.ID,
Severity: conf.Checks[check.ID],
Category: check.Category,
Success: passes,
}
if passes {
result.Message = check.SuccessMessage
} else {
result.Message = check.FailureMessage
}
return result
}
func getExemptKey(checkID string) string {
return fmt.Sprintf("polaris.fairwinds.com/%s-exempt", checkID)
}
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
results := ResultSet{}
checkIDs := getSortedKeys(conf.Checks)
objectAnnotations := controller.ObjectMeta.GetAnnotations()
for _, checkID := range checkIDs {
exemptValue := objectAnnotations[getExemptKey(checkID)]
if strings.ToLower(exemptValue) == "true" {
continue
}
check, err := resolveCheck(conf, checkID, controller, config.TargetPod, false)
if err != nil {
return nil, err
} else if check == nil {
continue
}
passes, err := check.CheckPod(&controller.PodSpec)
if err != nil {
return nil, err
}
results[check.ID] = makeResult(conf, check, passes)
}
return results, nil
}
func applyControllerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
results := ResultSet{}
checkIDs := getSortedKeys(conf.Checks)
objectAnnotations := controller.ObjectMeta.GetAnnotations()
for _, checkID := range checkIDs {
exemptValue := objectAnnotations[getExemptKey(checkID)]
if strings.ToLower(exemptValue) == "true" {
continue
}
check, err := resolveCheck(conf, checkID, controller, config.TargetController, false)
if err != nil {
return nil, err
} else if check == nil {
continue
}
passes, err := check.CheckController(controller.OriginalObjectJSON)
if err != nil {
return nil, err
}
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)
objectAnnotations := controller.ObjectMeta.GetAnnotations()
for _, checkID := range checkIDs {
exemptValue := objectAnnotations[getExemptKey(checkID)]
if strings.ToLower(exemptValue) == "true" {
continue
}
check, err := resolveCheck(conf, checkID, controller, config.TargetContainer, isInit)
if err != nil {
return nil, err
} else if check == nil {
continue
}
var passes bool
if check.SchemaTarget == config.TargetPod {
podCopy := controller.PodSpec
podCopy.InitContainers = []corev1.Container{}
podCopy.Containers = []corev1.Container{*container}
passes, err = check.CheckPod(&podCopy)
} else {
passes, err = check.CheckContainer(container)
}
if err != nil {
return nil, err
}
results[check.ID] = makeResult(conf, check, passes)
}
return results, nil
}
func getSortedKeys(m map[string]config.Severity) []string {
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

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