Compare commits

..
72 Commits
Author SHA1 Message Date
jdesouza be3401cc9d Merge branch 'master' into js/gateway 2026-08-28 15:21:42 -03:00
renovate[bot]andlnx01 4fb77d3b8c Update all non-major dependencies (#1232)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-28 15:15:31 -03:00
jdesouza 8f61e0c68a Merge branch 'master' into js/gateway 2026-08-28 15:11:37 -03:00
jdesouza c2ccdb4a5d INS-2845: Prepare polaris for unifying circleci (#1228)
* Terraform plan

* Terraform plan

* Fix

* Fix

* Lint fixes

* Lint fixes

* Fix

* Remove file

* Fixed panic

* Fixed comment

* Reverted

* Removed unused param
2026-08-28 14:08:54 -03:00
jdesouza b860eb798b Add support to Gateway checks in Polaris 2026-08-28 10:39:40 -03:00
renovate[bot]andlnx01 cdbb35f449 Update all non-major dependencies (#1231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-26 11:50:04 -03:00
renovate[bot]andlnx01 c0e23a233b Update cimg/go Docker tag to v1.26.6 (#1229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-21 10:45:05 -03:00
renovate[bot]andlnx01 3bfea9bf10 Update module github.com/sirupsen/logrus to v1.10.0 (#1227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-20 14:05:50 -03:00
jdesouza 236ca14fca INS-2798: Refactor polaris to make it more compatible with other oss projects (#1226)
* INS-2798: Refactor polaris to make it more compatible with other oss projects

* INS-2798: Refactor polaris to make it more compatible with other oss projects

* Fix
2026-08-19 12:19:00 -03:00
renovate[bot]andlnx01 9fe4a72c77 Update module github.com/stretchr/testify to v1.12.0 (#1224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-17 08:48:48 -03:00
Terraform User dafa0b74fc Managed by Terraform 2026-08-06 16:10:01 +00:00
f0a370c757 Update dependency rok8s to v16 (#1221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: jdesouza <james@fairwinds.com>
2026-08-06 09:26:30 -03:00
cb741e3eb1 Update all non-major dependencies (#1219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: jdesouza <james@fairwinds.com>
2026-08-06 09:14:18 -03:00
renovate[bot]andlnx01 b65142e8be Pin dependency rok8s to 14.4.0 (#1218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-06 08:58:02 -03:00
Terraform User 31bc0fdb84 Managed by Terraform 2026-08-05 14:12:49 +00:00
292a1c257c Bump k8s.io/client-go from 0.35.4 to 0.36.3 (#1217)
* Bump k8s.io/client-go from 0.35.4 to 0.36.3

Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.35.4 to 0.36.3.
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.35.4...v0.36.3)

---
updated-dependencies:
- dependency-name: k8s.io/client-go
  dependency-version: 0.36.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Bump sigs.k8s.io/controller-runtime to v0.24.1

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vitor Vezani <vitor.vezani@fairwinds.com>
2026-08-03 11:03:11 -03:00
dependabot[bot]andlnx01 06ef2d3f70 Bump k8s.io/apimachinery from 0.36.2 to 0.36.3 (#1216)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.36.2 to 0.36.3.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.2...v0.36.3)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 10:44:28 -03:00
Vitor Rodrigo Vezani 4bdf5315b6 Add pod support to ResourceProvider and update related tests (#1214) 2026-07-21 13:19:25 -03:00
dependabot[bot]andlnx01 278cb9008c Bump golang.org/x/net from 0.51.0 to 0.55.0 (#1208)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.51.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.51.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 18:09:58 -03:00
d6858e0fe4 Bump alpine from 3.23.4 to 3.24.1 (#1209)
Bumps alpine from 3.23.4 to 3.24.1.

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: 3.24.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vitor Rodrigo Vezani <vitor.vezani@fairwinds.com>
2026-07-06 17:36:37 -03:00
dependabot[bot]andlnx01 78fe4cb11c Bump k8s.io/apimachinery from 0.36.1 to 0.36.2 (#1210)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.36.1 to 0.36.2.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.1...v0.36.2)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 17:33:43 -03:00
dependabot[bot]andlnx01 387e787a40 Bump k8s.io/apimachinery from 0.35.4 to 0.36.1 (#1197)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.35.4 to 0.36.1.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.35.4...v0.36.1)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.36.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 08:46:46 -03:00
Terraform User 1fdfec73a1 Managed by Terraform 2026-04-27 18:39:00 +00:00
Vitor Rodrigo Vezani aee298e2e0 add notice to include registry change and immutable images notice (#1191)
* add notice to include registry change and immutable images notice

* try to fix tests

* Refactor goreleaser script to conditionally skip Docker tags based on release status

* remove if
2026-04-27 12:42:05 -03:00
Vitor Rodrigo Vezani 0444cf92bc unify release process along with others OSS projects (#1192)
* unify release process along with others OSS projects

* remove docker loging

* override polaris image repository for tests

* fix polaris

* adding snapshot back

* store snapshot version into circleci workspace and load with kind on k8s cluster

* add test back

* add test back

* fix polaris config
2026-04-27 12:24:11 -03:00
Terraform User 50e4c63079 Managed by Terraform 2026-04-24 20:23:41 +00:00
Terraform User 7d053a6cbd Managed by Terraform 2026-04-23 15:34:04 +00:00
Terraform User d50336387b Managed by Terraform 2026-04-23 14:42:23 +00:00
Vitor Rodrigo Vezani 1554c90ece Add GCR support (#1189) 2026-04-22 10:45:37 -03:00
jdesouza 9dfb4262e6 INS-2239: Fix polaris vulnerabilities (#1187)
* Reapply "Fix vulnerabilities"

This reverts commit 27878a4670.

* Fix

* Fix

* Fix

* Fix
2026-04-21 15:32:09 -03:00
jdesouza 27878a4670 Revert "Fix vulnerabilities"
This reverts commit a46eeb28b6.
2026-04-21 14:50:44 -03:00
jdesouza a46eeb28b6 Fix vulnerabilities 2026-04-21 14:49:25 -03:00
Vitor Rodrigo Vezani fd862afadc remove adhoc envsubst process on goreleaser (#1186)
* remove adhoc envsubst process

* use envOrDefault instead of sprig's default
2026-04-21 13:05:27 -03:00
Terraform User ee9f568ebd Managed by Terraform 2026-04-15 20:14:16 +00:00
jdesouza 47c7deddfd INS-2152: polaris libs bump (#1182)
* Upgrade libs

* INS-2152: polaris libs bump
2026-04-01 15:17:52 -03:00
jdesouza 2007a2c317 INS-2026: Bump libs and fix vulnerabilities for polaris (#1176)
* Bump

* Bump

* Bump

* Bump

* Bump

* Fix

* FIx

* FIx

* Fix
2026-03-09 14:16:16 -03:00
jdesouza a5af7bb34e INS-1950: Go 1.26 and bump libs for polaris (#1172)
* Go 1.26

* Go 1.26

* Go 1.26

* Go 1.26
2026-02-24 12:22:14 -03:00
jdesouza 893e5de589 Revert "Go 1.26"
This reverts commit f6eed6589d.
2026-02-23 08:23:23 -03:00
jdesouza f6eed6589d Go 1.26 2026-02-23 08:22:16 -03:00
jdesouza 517c8899d7 INS-1829: Bump libs and fix vulnerabilities (#1171)
* Fix vulnerability

* Bump
2026-01-30 16:21:23 -03:00
jdesouza e2df222ded INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability (#1162)
* INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability

* INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability
2025-12-08 10:50:39 -03:00
jdesouza d45d67cdd2 Revert "INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability"
This reverts commit e8a113388e.
2025-12-08 09:48:07 -03:00
jdesouza e8a113388e INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability 2025-12-08 09:44:58 -03:00
jdesouza 21ef1f495c INS-1562: Fix polaris vulnerabilities (#1156) 2025-10-31 11:01:10 -03:00
jdesouza ec1ba2f2db INS-1303: fix CVE-2025-47907 (#1141)
* Revert "Revert "INS-1303: fix CVE-2025-47907""

This reverts commit 39fb27c6dd.

* INS-1303: fix CVE-2025-47907
2025-08-18 13:01:19 -03:00
jdesouza 39fb27c6dd Revert "INS-1303: fix CVE-2025-47907"
This reverts commit e51b90ef79.
2025-08-18 10:48:27 -03:00
jdesouza e51b90ef79 INS-1303: fix CVE-2025-47907 2025-08-18 10:46:11 -03:00
Andy Suderman 4de082b443 docs: update documentation from template (#1140) 2025-08-01 13:24:25 -06:00
dependabot[bot]andlnx01 264dc51f4c Bump sigs.k8s.io/yaml from 1.4.0 to 1.5.0 (#1127)
---
updated-dependencies:
- dependency-name: sigs.k8s.io/yaml
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:23:39 -03:00
jdesouza 45b1f6a1a5 Bumped libs (#1139) 2025-07-25 10:18:03 -03:00
Vitor Rodrigo Vezaniandjdesouza 38e540e0cf Remove flags from audit command and remove auth command (#1138)
* update libs

* Remove unused authentication code and dependencies from the Polaris project, streamlining the audit process by eliminating the upload insights feature and related flags.

* remove insights reporter

* remove lingering libs

* update docs

* INS-1251: Polaris: upgrade github.com/qri-io/jsonschema to v0.2.1 (#1135)

* Bump lins

* Code refactoring

* Fixign issues

* Fixing issues

* Fixing issues

* Fixing issues

* [WIP]

* [WIP]

* [WIP]

* Trying to fix tests

* Trying to fix tests

* Fixing issues

* Fixing issues

* Fixing issues

* Fixing issues

* Fixing issues

* Fixing issues

* Revert go mod

* Revert go mod

* Revert go mod

* Revert go mod

* Fixing issues

* Fixing issue

* Code refactoring

* Updating json schema version

* Updating json schema version

* fix go mod

* fix go sum

---------

Co-authored-by: jdesouza <james@fairwinds.com>
2025-07-24 16:48:23 -03:00
jdesouza 2b17c31957 INS-1251: Polaris: upgrade github.com/qri-io/jsonschema to v0.2.1 (#1135)
* Bump lins

* Code refactoring

* Fixign issues

* Fixing issues

* Fixing issues

* Fixing issues

* [WIP]

* [WIP]

* [WIP]

* Trying to fix tests

* Trying to fix tests

* Fixing issues

* Fixing issues

* Fixing issues

* Fixing issues

* Fixing issues

* Fixing issues

* Revert go mod

* Revert go mod

* Revert go mod

* Revert go mod

* Fixing issues

* Fixing issue

* Code refactoring

* Updating json schema version

* Updating json schema version
2025-07-24 13:46:37 -03:00
jdesouza 813d9c0a2a INS-1097: Fix CVE-2025-22874 for polaris (#1122)
* Bumping polaris libs

* Fixing lint

* Fixed vuln

* Fixed vuln

* Fixed vuln

* Fixed vuln
2025-06-17 08:58:10 -03:00
jdesouza d7d30ac611 INS-933 - pullPolicyNotAlways is crashing webhook mutations (#1115)
* Debugging

* Debugging

* Trying to fix

* Revert

* Debugging

* Debugging

* Debugging

* Fixing issue

* Fixing issue

* Fixing issue

* Fixing issue

* Fixing issue

* Fixing issue
2025-04-21 17:07:12 -03:00
dependabot[bot]andlnx01 8fe7a94260 Bump golang.org/x/net from 0.33.0 to 0.36.0 (#1108)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.36.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 13:26:47 -03:00
dependabot[bot]andlnx01 f29571e8bb Bump k8s.io/client-go from 0.32.0 to 0.32.2 (#1103)
Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.32.0 to 0.32.2.
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.32.0...v0.32.2)

---
updated-dependencies:
- dependency-name: k8s.io/client-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 13:21:50 -03:00
dependabot[bot]andlnx01 a74714b5e5 Bump k8s.io/apimachinery from 0.32.0 to 0.32.2 (#1102)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.32.0 to 0.32.2.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.32.0...v0.32.2)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 13:16:16 -03:00
dependabot[bot]andlnx01 ba04b7e330 Bump sigs.k8s.io/controller-runtime from 0.19.3 to 0.20.2 (#1101)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.19.3 to 0.20.2.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.19.3...v0.20.2)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 12:42:36 -03:00
youM d70591b1b8 fixed pdbMinAvailableGreaterThanHPAMinReplicas and added validation for pdbMinAvailableEqualToHPAMinReplicas. (#1073)
* rename pdbMinAvailableGreaterThanHPAMinReplicas to pdbMinAvailableGreaterThanOrEqualToHPAMinReplicas

* modify doc

* Revert "modify doc"

This reverts commit faffffc298cde23a99c5e19528123f249d92d314.

* Revert "rename pdbMinAvailableGreaterThanHPAMinReplicas to pdbMinAvailableGreaterThanOrEqualToHPAMinReplicas"

This reverts commit b93f283acc279b90c7e7a9d8dbbe53b2f8ab9a31.

* fix test

* fix impl
2025-04-14 11:36:32 -03:00
jdesouza 80e6f7214e INSIGHTS-593 Fix Polaris vulerabilities (#1094) 2025-01-02 13:01:37 -03:00
Vitor Rodrigo Vezani 1d20a16cb3 update supporting libs and bump alpine to 3.21 (#1093)
* bump alpine

* bump libs
2024-12-19 09:20:30 -07:00
dependabot[bot]andlnx01 37edbc7159 Bump k8s.io/client-go from 0.31.2 to 0.31.3 (#1084)
Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.31.2 to 0.31.3.
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.31.2...v0.31.3)

---
updated-dependencies:
- dependency-name: k8s.io/client-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-05 06:17:16 -07:00
dependabot[bot]andlnx01 d451480acf Bump k8s.io/api from 0.31.2 to 0.31.3 (#1083)
Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.31.2 to 0.31.3.
- [Commits](https://github.com/kubernetes/api/compare/v0.31.2...v0.31.3)

---
updated-dependencies:
- dependency-name: k8s.io/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-05 08:16:46 -05:00
705e6e4016 Bump sigs.k8s.io/controller-runtime from 0.19.1 to 0.19.2 (#1086)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.19.1 to 0.19.2.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.19.1...v0.19.2)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vitor Rodrigo Vezani <vitor.vezani@fairwinds.com>
2024-12-05 10:12:36 -03:00
dependabot[bot]andlnx01 2d9aa54e68 Bump k8s.io/apimachinery from 0.31.2 to 0.31.3 (#1087)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.31.2 to 0.31.3.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.31.2...v0.31.3)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-05 10:09:26 -03:00
jdesouzaandAndy Suderman 4dd3a81bbd INSIGHTS-475 Add 3 new checks to polaris (#1082)
* INSIGHTS-448 Add Two Polaris Checks

* Added another chec

* Added another chec

* Added another chec

* Added another chec

* Added another chec

* Added another chec

* Fixing issue

* Fixing issue

* Added another validation

* Added some tests cases

* Added some tests cases

* Update pkg/config/checks/hostProcess.yaml

* Update pkg/validator/pod_test.go

---------

Co-authored-by: Andy Suderman <andy@fairwinds.com>
2024-11-13 08:24:37 -03:00
dependabot[bot]andlnx01 4b87bafbb9 Bump k8s.io/client-go from 0.31.1 to 0.31.2 (#1078)
Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.31.1 to 0.31.2.
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.31.1...v0.31.2)

---
updated-dependencies:
- dependency-name: k8s.io/client-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-08 11:48:05 -03:00
dependabot[bot]andlnx01 62effa36b6 Bump sigs.k8s.io/controller-runtime from 0.19.0 to 0.19.1 (#1079)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.19.0 to 0.19.1.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.19.0...v0.19.1)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-08 11:43:26 -03:00
dependabot[bot]andlnx01 913988e44c Bump github.com/fatih/color from 1.17.0 to 1.18.0 (#1081)
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.17.0...v1.18.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-08 10:41:00 -03:00
dependabot[bot]andlnx01 f281cd1e33 Bump k8s.io/api from 0.31.1 to 0.31.2 (#1077)
Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.31.1 to 0.31.2.
- [Commits](https://github.com/kubernetes/api/compare/v0.31.1...v0.31.2)

---
updated-dependencies:
- dependency-name: k8s.io/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-08 10:35:50 -03:00
Vitor Rodrigo VezaniandAndy Suderman 073847559a Add --merge-config flag to support merging with default configuration (#1075)
* add config merge support

* fix indentation

* Update cmd/polaris/root.go

Co-authored-by: Andy Suderman <andy@fairwinds.com>

---------

Co-authored-by: Andy Suderman <andy@fairwinds.com>
2024-10-22 15:31:18 -03:00
Grégoire MartiniandVitor Vezani 9b5438d864 Fix: rolebindingRolePodExecAttach check (#1070)
* Fix: rolebindingRolePodExecAttach check

Fix the case of a RoleBinding that points to a ClusterRole.
In that case, we ignore the RoleBinding since it will be evaluated by the rolebindingClusterRolePodExecAttach check.

* add tests for role-binding that uses a cluster-role binding

---------

Co-authored-by: Vitor Vezani <vitor.vezani@fairwinds.com>
2024-10-02 10:43:24 -06:00
160 changed files with 2755 additions and 1994 deletions
+141 -142
View File
@@ -1,183 +1,182 @@
## DO NOT EDIT - Managed by Terraform
version: 2.1
orbs:
rok8s: fairwinds/rok8s-scripts@12
rok8s: fairwinds/rok8s-scripts@16.0.0
oss-docs: fairwinds/oss-docs@0
executors:
vm:
machine:
enabled: true
commands:
install_goreleaser_dependencies:
description: Installs dependencies for CI scripts
steps:
- run: apk update
# gettext provides envsubst
- run: apk add gettext
# Register other docker platforms, to build arm64.
# This shouldn't be needed, why TBD.
- run: docker run --privileged --rm tonistiigi/binfmt --install all
references:
set_environment_variables: &set_environment_variables
run:
name: Set Environment Variables
command: |
echo 'export CI_SHA1=$CIRCLE_SHA1' >> ${BASH_ENV}
echo 'export CI_BRANCH=$CIRCLE_BRANCH' >> ${BASH_ENV}
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}
echo 'export GOFLAGS=-mod=mod' >> ${BASH_ENV}
echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $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
echo "Installing KIND"
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.19.0/kind-linux-amd64
chmod 0755 kind-linux-amd64
sudo mv kind-linux-amd64 /usr/local/bin/kind
kind version
echo "Installing Kubectl"
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.25.10/bin/linux/amd64/kubectl
chmod 0755 kubectl
sudo mv kubectl /usr/local/bin/
kubectl version --client
echo "Creating Kubernetes Cluster with Kind"
kind create cluster --wait=90s --image kindest/node:v1.25.9@sha256:c08d6c52820aa42e533b70bce0c2901183326d86dcdcbedecc9343681db45161
docker ps -a
kubectl version
echo "Installing Helm"
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
echo "Installing cert-manager"
helm repo add jetstack https://charts.jetstack.io
helm repo update
echo "Helm install"
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.12.1 --set "installCRDs=true" --wait --create-namespace
echo "Install cert-manager successful"
test_binary_dashboard: &test_binary_dashboard
run:
name: Test Dashboard
command: |
go run main.go dashboard --port 3000 --audit-path ./pkg/config/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_k8s: &test_k8s
run:
name: Test Kubernetes Deployments
command: |
if [[ -z $CIRCLE_PR_NUMBER ]]; then
./test/webhook_test.sh
./test/kube_dashboard_test.sh
else
echo "Skipping Kubernetes tests for forked PR"
fi
install_vault_alpine: &install_vault_alpine
install_vault_machine: &install_vault_machine
run:
name: install hashicorp vault
command: |
apk --update add curl yq
sudo apt-get update -y && sudo apt-get install -y curl unzip
cd /tmp
curl -LO https://releases.hashicorp.com/vault/1.13.2/vault_1.13.2_linux_amd64.zip
sha256sum vault_1.13.2_linux_amd64.zip | grep f7930279de8381de7c532164b4a4408895d9606c0d24e2e9d2f9acb5dfe99b3c
unzip vault_1.13.2_linux_amd64.zip
mv vault /usr/bin/vault
jobs:
test_k8s:
working_directory: ~/polaris
resource_class: medium
executor: vm
steps:
- checkout
- *set_environment_variables
- *install_k8s
- *test_k8s
curl -LO https://releases.hashicorp.com/vault/1.21.4/vault_1.21.4_linux_amd64.zip
echo '889b681990fe221b884b7932fa9c9dd0ee9811b9349554f1aa287ab63c9f3dae vault_1.21.4_linux_amd64.zip' | sha256sum -c
unzip -o vault_1.21.4_linux_amd64.zip
sudo mv vault /usr/bin/vault
setup_qemu_binfmt: &setup_qemu_binfmt
run:
name: Setup QEMU for multi-arch Docker builds
command: |
sudo apt-get update -y
sudo apt-get install -y qemu-user-static binfmt-support
docker buildx create --use || true
docker buildx inspect --bootstrap
e2e_configuration: &e2e_configuration
executor: golang-exec
pre_script: e2e/pre.sh
script: e2e/test.sh
command_runner_image: quay.io/reactiveops/ci-images:v14.1-bullseye
enable_docker_layer_caching: true
store-test-results: /tmp/test-results
attach-workspace: true
requires:
- test
- snapshot
filters:
branches:
only: /.*/
tags:
ignore: /.*/
executors:
golang-exec:
docker:
- image: cimg/go:1.26.7
jobs:
test:
docker:
- image: cimg/go:1.22
- image: cimg/go:1.26.7
steps:
- checkout
- *set_environment_variables
- run: go vet ./...
- run: go test ./... -coverprofile=coverage.txt -covermode=count
- *test_binary_dashboard
build_and_push:
working_directory: /go/src/github.com/fairwindsops/polaris/
- run:
name: Go Mod Download
command: go mod download && go mod verify
- run:
name: golangci-lint
command: |
curl -fsSL -o golangci-lint.tar.gz https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-amd64.tar.gz
echo '8df580d2670fed8fa984aac0507099af8df275e665215f5c7a2ae3943893a553 golangci-lint.tar.gz' | sha256sum -c
tar -xzf golangci-lint.tar.gz
mv golangci-lint-2.12.2-linux-amd64/golangci-lint "$(go env GOPATH)/bin/golangci-lint"
golangci-lint run --timeout 5m
- run:
name: test
command: |
go test -v -coverprofile=coverage.txt -covermode=atomic ./...
go vet ./...
- run:
name: Test Dashboard
command: ./test/dashboard_test.sh
snapshot:
machine:
image: ubuntu-2204:current
resource_class: large
steps:
- checkout
- *setup_qemu_binfmt
- run:
name: Run GoReleaser snapshot
command: |
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd):/workspace" -w /workspace \
-e CIRCLE_SHA1 \
-e CIRCLE_BRANCH \
-e CIRCLE_TAG \
goreleaser/goreleaser:v2.17.1 release --snapshot --skip=sign
- run:
name: Save snapshot amd64 image for e2e
command: |
mkdir -p /tmp/workspace/docker_save
docker save us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}-amd64 > /tmp/workspace/docker_save/polaris_${CIRCLE_SHA1}-amd64.tar
- persist_to_workspace:
root: /tmp/workspace/
paths:
- docker_save
- store_artifacts:
path: dist
destination: snapshot
release:
machine:
image: ubuntu-2204:current
resource_class: large
shell: /bin/bash
docker:
# The goreleaser image tag determins the version of Go.
# Manually check goreleaser images for their version of Go.
# Ref: https://hub.docker.com/r/goreleaser/goreleaser/tags
- image: goreleaser/goreleaser:v2.3.2
steps:
- checkout
- setup_remote_docker
- *install_vault_alpine
- *install_vault_machine
- rok8s/get_vault_env:
vault_path: repo/global/env
- rok8s/get_vault_env:
vault_path: repo/polaris/env
- *set_environment_variables
- run: docker login quay.io -u="${FAIRWINDS_QUAY_USER}" -p="${FAIRWINDS_QUAY_TOKEN}"
- install_goreleaser_dependencies
- run: scripts/goreleaser.sh
- run:
name: docker login Google Artifact Registry
command: |
echo "$GCP_ARTIFACTREADWRITE_JSON_KEY" | base64 -d | docker login -u _json_key --password-stdin us-docker.pkg.dev
- *setup_qemu_binfmt
- run:
name: Run GoReleaser release
command: |
export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd):/workspace" -w /workspace \
-v "${HOME}/.docker:/root/.docker" \
-e GORELEASER_CURRENT_TAG \
-e CIRCLE_TAG \
-e CIRCLE_SHA1 \
-e GO111MODULE=on \
-e GITHUB_TOKEN \
-e VAULT_ADDR \
-e VAULT_TOKEN \
goreleaser/goreleaser:v2.17.1 release
workflows:
version: 2
release:
test_and_build:
jobs:
- test:
filters:
tags:
ignore: /.*/
- snapshot:
requires:
- test
filters:
branches:
only: /.*/
- build_and_push:
tags:
ignore: /.*/
- rok8s/kubernetes_e2e_tests:
name: "kubernetes e2e"
kind_node_image: "kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a"
<<: *e2e_configuration
release:
jobs:
- test:
filters:
branches:
ignore: /.*/
tags:
only: /v.*/
- release:
requires:
- test
context: org-global
filters:
branches:
ignore: /pull\/[0-9]+/
ignore: /.*/
tags:
ignore: /^testing-.*/
- test_k8s:
requires:
- build_and_push
filters:
branches:
ignore: /pull\/[0-9]+/
tags:
ignore: /^testing-.*/
only: /v.*/
- oss-docs/publish-docs:
requires:
- release
repository: polaris
filters:
branches:
ignore: /.*/
tags:
ignore: /^testing-.*/
only: /v.*/
+1 -1
View File
@@ -1,6 +1,6 @@
# The action uses an own Dockerfile on purpose because the root Dockerfile takes way too long to build for an action
FROM alpine:3.17
FROM alpine:3.24
RUN apk add --no-cache \
bash \
-20
View File
@@ -1,20 +0,0 @@
## DO NOT EDIT - Managed by Terraform
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/docs"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
build-int:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6.0.2
- name: Setup polaris
uses: ./.github/actions/setup-polaris
with:
@@ -18,7 +18,7 @@ jobs:
build-ext:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6.0.2
- name: Setup polaris
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
-2
View File
@@ -1,5 +1,3 @@
# goreleaser is run via a wrapper that creates .goreleaser.yml from .goreleaser.yml.envsubst
.goreleaser.yml
# dist
# # Binaries for programs and plugins
.go-version
+116
View File
@@ -0,0 +1,116 @@
## DO NOT EDIT - Managed by Terraform
# yaml-language-server: $$schema=https://goreleaser.com/static/schema.json
version: 2
project_name: polaris
before:
hooks:
- go mod download
builds:
- id: polaris
ldflags:
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
goarm:
- "6"
- "7"
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
brews:
- name: polaris
repository:
owner: FairwindsOps
name: homebrew-tap
directory: Formula
description: Open Source Best Practices for Kubernetes
url_template: "https://github.com/FairwindsOps/polaris/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
test: |
system "#{bin}/polaris version"
release:
disable: '{{ eq (envOrDefault "GORELEASER_SKIP_RELEASE" "false") "true" }}'
prerelease: auto
github:
owner: FairwindsOps
name: polaris
footer: |
You can verify the signatures of both the checksums.txt file and the published docker images using [cosign](https://github.com/sigstore/cosign).
```bash
cosign verify-blob checksums.txt --bundle=checksums.txt.sigstore.json --key https://artifacts.fairwinds.com/cosign-p256.pub
```
```bash
cosign verify us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }} --key https://artifacts.fairwinds.com/cosign-p256.pub
```
checksum:
name_template: "checksums.txt"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
signs:
- cmd: cosign
signature: "${artifact}.sigstore.json"
args:
- "sign-blob"
- "--key=hashivault://cosign-p256"
- "--bundle=${signature}"
- "${artifact}"
- "--yes"
artifacts: all
docker_signs:
- artifacts: all
args: ["sign", "--key=hashivault://cosign-p256", "us-docker.pkg.dev/fairwinds-ops/oss/polaris@${digest}", "-r", "--yes"]
dockers:
- image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-amd64"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-amd64"
use: buildx
dockerfile: Dockerfile
build_flag_templates:
- "--platform=linux/amd64"
- image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-arm64v8"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-arm64v8"
use: buildx
goarch: arm64
goos: linux
dockerfile: Dockerfile
build_flag_templates:
- "--platform=linux/arm64/v8"
- image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-armv7"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-armv7"
use: buildx
goarch: arm
goarm: 7
goos: linux
dockerfile: Dockerfile
build_flag_templates:
- "--platform=linux/arm/v7"
docker_manifests:
- name_template: us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}
image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-amd64"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-arm64v8"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-armv7"
- name_template: us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}
image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-amd64"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-arm64v8"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-armv7"
-114
View File
@@ -1,114 +0,0 @@
version: 2
checksum:
name_template: 'checksums.txt'
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
env:
- GOBIN={{ .Env.TMPDIR }}/go-bin
before:
hooks:
- go mod download
builds:
- id: polaris
ldflags:
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
goarm:
- 6
- 7
archives:
- id: polaris
builds: ["polaris"]
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
signs:
- cmd: cosign
args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}"]
artifacts: checksum
release:
# This is replaced using `envsubst`, depending on the git branch.
disable: ${skip_release}
prerelease: auto
footer: |
You can verify the signature of the checksums.txt file using [cosign](https://github.com/sigstore/cosign).
```
cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub
```
brews:
- name: polaris
# This is replaced using `envsubst`, depending on the git branch.
skip_upload: ${skip_release}
repository:
owner: FairwindsOps
name: homebrew-tap
directory: Formula
description: Open Source Best Practices for Kubernetes
url_template: "https://github.com/FairwindsOps/polaris/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
test: |
system "#{bin}/polaris version"
dockers:
# There are multiple images to match the `--platform` docker build flag with
# combinations of `GOOS`, `GOARCH`, and `GOARM`
- image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
use: buildx
goarch: arm64
goos: linux
build_flag_templates:
- "--platform=linux/arm64"
docker_manifests:
# Create DOcker manifests that make multiple architectures available within a tag,
# and provide partial-version tags like 2, and 2.2.
- name_template: quay.io/fairwinds/polaris:{{ .FullCommit }}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Env.feature_docker_tag }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_feature_docker_tags}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:latest
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Tag }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Major }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Major }}.{{ .Minor }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
+2 -2
View File
@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.24.1
LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \
org.opencontainers.image.vendor="FairwindsOps, Inc." \
@@ -10,7 +10,7 @@ LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \
org.opencontainers.image.licenses="Apache License 2.0"
WORKDIR /usr/local/bin
RUN apk -U upgrade
# Install ca-certs
RUN apk --no-cache add ca-certificates
RUN addgroup -S polaris && adduser -u 1200 -S polaris -G polaris
+39 -12
View File
@@ -29,19 +29,50 @@ Polaris can be run in three different modes:
## Documentation
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
## Notice: Registry Migration and Immutable Images (v10.1.8 → v10.2.0)
Starting with **v10.2.0**:
- Images moved to `us-docker.pkg.dev/fairwinds-ops/oss/polaris`
- `quay.io/fairwinds/polaris` is deprecated
### Required action
```diff
- quay.io/fairwinds/polaris:<tag>
+ us-docker.pkg.dev/fairwinds-ops/oss/polaris:<tag>
```
---
## Immutable and signed images
* Images are now **signed**
* Tags are **immutable**
* No more floating tags:
* `v10`
* `v10.1`
* `latest`
Use full version tags:
```
us-docker.pkg.dev/fairwinds-ops/oss/polaris:v<major>.<minor>.<patch>
```
Or pin by digest:
```
us-docker.pkg.dev/fairwinds-ops/oss/polaris@sha256:<digest>
```
<!-- Begin boilerplate -->
## Join the Fairwinds Open Source Community
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap,
and network with fellow Kubernetes users.
[Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-2na8gtwb4-DGQ4qgmQbczQyB2NlFlYQQ)
or
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
<a href="https://insights.fairwinds.com/auth/register/">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png"
alt="Love Fairwinds Open Source? Automate Fairwinds Open Source for free with Fairwinds Insights. Click to learn more" />
</a>
## Other Projects from Fairwinds
@@ -56,9 +87,5 @@ Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_
If you're interested in running Polaris in multiple clusters,
tracking the results over time, integrating with Slack, Datadog, and Jira,
or unlocking other functionality, check out
[Fairwinds Insights](https://fairwinds.com/pricing),
[Fairwinds Insights](https://fairwinds.com/insights),
a platform for auditing and enforcing policy in Kubernetes clusters.
<a href="https://fairwinds.com/pricing">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_Polaris_Ad.png" alt="Fairwinds Insights" />
</a>
+21 -77
View File
@@ -24,14 +24,8 @@ import (
"net/http"
"os"
"os/exec"
"strings"
workloads "github.com/fairwindsops/insights-plugins/plugins/workloads"
workloadsPkg "github.com/fairwindsops/insights-plugins/plugins/workloads/pkg"
"github.com/fairwindsops/polaris/pkg/auth"
cfg "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/insights"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
@@ -55,9 +49,6 @@ var (
auditNamespace string
severityLevel string
skipSslValidation bool
uploadInsights bool
clusterName string
quiet bool
)
func init() {
@@ -79,9 +70,6 @@ func init() {
auditCmd.PersistentFlags().StringVar(&auditNamespace, "namespace", "", "Namespace to audit. Only applies to in-cluster audits")
auditCmd.PersistentFlags().StringVar(&severityLevel, "severity", "", "Severity level used to filter results. Behaves like log levels. 'danger' is the least verbose (warning, danger)")
auditCmd.PersistentFlags().BoolVar(&skipSslValidation, "skip-ssl-validation", false, "Skip https certificate verification")
auditCmd.PersistentFlags().BoolVar(&uploadInsights, "upload-insights", false, "Upload scan results to Fairwinds Insights")
auditCmd.PersistentFlags().StringVar(&clusterName, "cluster-name", "", "Set --cluster-name to a descriptive name for the cluster you're auditing")
auditCmd.PersistentFlags().BoolVar(&quiet, "quiet", false, "Suppress the 'upload to Insights' prompt.")
}
var auditCmd = &cobra.Command{
@@ -120,23 +108,6 @@ var auditCmd = &cobra.Command{
os.Exit(1)
}
}
if uploadInsights && len(clusterName) == 0 {
logrus.Error("cluster-name is required when using --upload-insights")
os.Exit(1)
}
if uploadInsights {
if auditPath != "" {
logrus.Errorf("upload-insights and audit-path are not supported when used simultaneously")
os.Exit(1)
}
if !auth.IsLoggedIn() {
err := auth.HandleLogin(insightsHost)
if err != nil {
logrus.Errorf("error handling logging: %v", err)
os.Exit(1)
}
}
}
ctx := context.TODO()
k, err := kube.CreateResourceProvider(ctx, auditPath, resourceToAudit, config)
@@ -145,49 +116,13 @@ var auditCmd = &cobra.Command{
os.Exit(1)
}
auditData, err := validator.RunAudit(config, k)
auditData, err := validator.RunAudit(context.Background(), config, k)
if err != nil {
logrus.Errorf("Error while running audit on resources: %v", err)
os.Exit(1)
}
if uploadInsights {
auth, err := auth.GetAuth(insightsHost)
if err != nil {
logrus.Errorf("getting auth: %v", err)
os.Exit(1)
}
// fetch workloads using workload plugin... or should we adapt the workloads from above?
dynamicClient, restMapper, clientSet, host, err := kube.GetKubeClient(ctx, "")
if err != nil {
logrus.Errorf("getting the kubernetes client: %v", err)
os.Exit(1)
}
k8sResources, err := workloadsPkg.CreateResourceProviderFromAPI(ctx, dynamicClient, restMapper, clientSet, host)
if err != nil {
logrus.Errorf("creating resource provider: %v", err)
os.Exit(1)
}
insightsClient := insights.NewHTTPClient(insightsHost, auth.Organization, auth.Token)
insightsReporter := insights.NewInsightsReporter(insightsClient)
wr := insights.WorkloadsReport{Version: workloads.Version, Payload: *k8sResources}
pr := insights.PolarisReport{Version: version, Payload: auditData}
logrus.Infof("Uploading to Fairwinds Insights organization '%s/%s'...", auth.Organization, clusterName)
err = insightsReporter.ReportAuditToFairwindsInsights(clusterName, wr, pr)
if err != nil {
logrus.Errorf("reporting audit file to insights: %v", err)
os.Exit(1)
}
os.Stderr.WriteString("\n\nSuccess! You can see your results at:")
os.Stderr.WriteString(fmt.Sprintf("\n\n%s/orgs/%s/clusters/%s/action-items\n\n", insightsHost, auth.Organization, clusterName))
} else {
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests, severityLevel)
if !quiet {
os.Stderr.WriteString("\n\n🚀 Upload your Polaris findings to Fairwinds Insights to see remediation advice, add teammates, integrate with Slack or Jira, and more:")
os.Stderr.WriteString("\n\n polaris " + strings.Join(os.Args[1:], " ") + " --upload-insights --cluster-name=my-cluster\n\n")
}
}
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests, severityLevel)
summary := auditData.GetSummary()
score := summary.GetScore()
@@ -254,17 +189,18 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
var outputBytes []byte
var err error
if outputFormat == "score" {
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
} else if outputFormat == "yaml" {
switch outputFormat {
case "score":
outputBytes = fmt.Appendf(nil, "%d\n", auditData.GetSummary().GetScore())
case "yaml":
var jsonBytes []byte
jsonBytes, err = json.Marshal(auditData)
if err == nil {
outputBytes, err = yaml.JSONToYAML(jsonBytes)
}
} else if outputFormat == "pretty" {
case "pretty":
outputBytes = []byte(auditData.GetPrettyOutput(useColor))
} else {
default:
outputBytes, err = json.MarshalIndent(auditData, "", " ")
}
if err != nil {
@@ -272,7 +208,10 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
os.Exit(1)
}
if outputURL == "" && outputFile == "" {
os.Stdout.Write(outputBytes)
if _, err := os.Stdout.Write(outputBytes); err != nil {
logrus.Errorf("Error writing audit to stdout: %v", err)
os.Exit(1)
}
} else {
if outputURL != "" {
req, err := http.NewRequest("POST", outputURL, bytes.NewBuffer(outputBytes))
@@ -282,11 +221,12 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
os.Exit(1)
}
if outputFormat == "json" {
switch outputFormat {
case "json":
req.Header.Set("Content-Type", "application/json")
} else if outputFormat == "yaml" {
case "yaml":
req.Header.Set("Content-Type", "application/x-yaml")
} else {
default:
req.Header.Set("Content-Type", "text/plain")
}
@@ -301,7 +241,11 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
os.Exit(1)
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
logrus.Errorf("Error closing response body: %v", err)
}
}()
body, err := io.ReadAll(resp.Body)
-81
View File
@@ -1,81 +0,0 @@
package cmd
import (
"github.com/fairwindsops/polaris/pkg/auth"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(authCmd)
authCmd.AddCommand(loginCmd)
authCmd.AddCommand(logoutCmd)
authCmd.AddCommand(statusCmd)
authCmd.AddCommand(tokenCmd)
}
var authCmd = &cobra.Command{
Use: "auth",
Short: "Authenticate polaris with Fairwinds Insights",
Long: `Authenticate polaris with Fairwinds Insights so better experience`,
}
var loginCmd = &cobra.Command{
Use: "login",
Short: "Authenticate polaris with Fairwinds Insights.",
Long: `Authenticate polaris with Fairwinds Insights.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.HandleLogin(insightsHost)
if err != nil {
logrus.Fatal(err)
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
var logoutCmd = &cobra.Command{
Use: "logout",
Short: "Log out of a Fairwinds Insights.",
Long: `Log out of a Fairwinds Insights.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.HandleLogout()
if err != nil {
logrus.Fatal(err)
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
var statusCmd = &cobra.Command{
Use: "status",
Short: "View authentication status.",
Long: `View authentication status.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.PrintStatus(insightsHost)
if err != nil {
logrus.Fatalf("printing status: %v", err)
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
var tokenCmd = &cobra.Command{
Use: "token",
Short: "Print the auth token gh is configured to use.",
Long: `Print the auth token gh is configured to use.`,
Run: func(cmd *cobra.Command, args []string) {
err := auth.PrintToken()
if err != nil {
logrus.Fatalf("printing token: %v", err)
}
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
+5 -2
View File
@@ -15,6 +15,7 @@
package cmd
import (
"context"
"fmt"
"net/http"
@@ -54,13 +55,15 @@ var dashboardCmd = &cobra.Command{
auditData := validator.ReadAuditFromFile(loadAuditFile)
auditDataPtr = &auditData
}
router, err := dashboard.GetRouter(config, auditPath, serverPort, basePath, auditDataPtr)
router, err := dashboard.GetRouter(context.Background(), config, auditPath, serverPort, basePath, auditDataPtr)
if err != nil {
logrus.Fatalf("error creating router: %v", err)
}
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
if _, err := w.Write([]byte("OK")); err != nil {
logrus.Errorf("Error writing health response: %v", err)
}
})
http.Handle("/", router)
+5 -2
View File
@@ -15,6 +15,7 @@
package cmd
import (
"context"
"errors"
"os"
@@ -43,11 +44,13 @@ var fixCommand = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
logrus.Debug("Setting up controller manager")
err := fix.Execute(config, filesPath, isTemplate, checksToFix...)
err := fix.Execute(context.Background(), config, filesPath, isTemplate, checksToFix...)
if err != nil {
if errors.Is(err, fix.ErrFilesPathRequired) {
logrus.Error("Please specify a files-path flag")
cmd.Help()
if helpErr := cmd.Help(); helpErr != nil {
logrus.Error(helpErr)
}
os.Exit(1)
}
logrus.Fatal(err)
+6 -3
View File
@@ -24,11 +24,11 @@ import (
)
var (
mergeConfig bool
configPath string
disallowExemptions bool
disallowConfigExemptions bool
disallowAnnotationExemptions bool
fixChecks bool
logLevel string
auditPath string
displayName string
@@ -42,6 +42,7 @@ var (
func init() {
// Flags
rootCmd.PersistentFlags().BoolVarP(&mergeConfig, "merge-config", "m", false, "If true, custom configuration will be merged with default configuration instead of replacing it.")
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Location of Polaris configuration file.")
rootCmd.PersistentFlags().StringVarP(&kubeContext, "context", "x", "", "Set the kube context.")
rootCmd.PersistentFlags().BoolVarP(&disallowExemptions, "disallow-exemptions", "", false, "Disallow any configured exemption.")
@@ -65,7 +66,7 @@ var rootCmd = &cobra.Command{
logrus.SetLevel(parsedLevel)
}
config, err = conf.ParseFile(configPath)
config, err = conf.MergeConfigAndParseFile(configPath, mergeConfig)
if err != nil {
logrus.Errorf("Error parsing config at %s: %v", configPath, err)
os.Exit(1)
@@ -86,7 +87,9 @@ var rootCmd = &cobra.Command{
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if !strings.HasPrefix(cmd.Use, "audit") {
os.Stderr.WriteString("\n\nWant more? Automate Polaris for free with Fairwinds Insights!\n🚀 https://fairwinds.com/insights-signup/polaris 🚀 \n")
if _, err := os.Stderr.WriteString("\n\nWant more? Automate Polaris for free with Fairwinds Insights!\n🚀 https://fairwinds.com/insights-signup/polaris 🚀 \n"); err != nil {
logrus.Error(err)
}
}
},
}
+2 -1
View File
@@ -15,6 +15,7 @@
package cmd
import (
"context"
"os"
"github.com/sirupsen/logrus"
@@ -76,7 +77,7 @@ var webhookCmd = &cobra.Command{
fwebhook.NewValidateWebhook(mgr, config)
}
if enableMutations {
fwebhook.NewMutateWebhook(mgr, config)
fwebhook.NewMutateWebhook(context.Background(), mgr, config)
}
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
+17
View File
@@ -24,6 +24,15 @@ key | default | description
`hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
`hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
`tlsSettingsMissing` | `warning` | Fails when an Ingress lacks TLS settings.
`gatewayTLSMissing` | `warning` | Fails when an HTTPS, GRPC, or terminating TLS Gateway listener lacks certificate references.
`gatewayAllowedRoutesAll` | `warning` | Fails when a Gateway listener allows Routes from every namespace.
`gatewayInsecureFrontendValidation` | `warning` | Fails when Gateway frontend client certificate validation allows insecure fallback.
`gatewayCrossNamespaceCertificateRef` | `warning` | Fails when a Gateway references a certificate in another namespace without a matching ReferenceGrant. Cluster audits only.
`httpRouteWildcardOrEmptyHost` | `warning` | Fails when an HTTPRoute omits hostnames or uses a wildcard hostname.
`httpRouteInsecureListener` | `warning` | Fails when an HTTPRoute serves application traffic over HTTP without a full HTTPS redirect. Cluster audits only.
`httpRouteCrossNamespaceBackendRef` | `warning` | Fails when an HTTPRoute references a backend in another namespace without a matching ReferenceGrant. Cluster audits only.
`httpRouteBackendTLSMissing` | `warning` | Fails when an HTTPRoute TLS backend lacks a BackendTLSPolicy or kgateway BackendConfigPolicy. Cluster audits only.
`kgatewayBackendTLSVerificationDisabled` | `warning` | Fails when a kgateway BackendConfigPolicy disables TLS certificate verification.
`sensitiveContainerEnvVar` | `danger` | Fails when the container sets potentially sensitive environment variables.
`sensitiveConfigmapContent` | `danger` | Fails when potentially sensitive content is detected in the ConfigMap keys or values.
`missingNetworkPolicy` | `warning`
@@ -40,6 +49,14 @@ key | default | description
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, not giving excessive access to the host network, and using read only file systems wherever possible.
### Gateway API
Gateway API separates listeners, routes, and backend TLS policy across different resources. Polaris checks standard `Gateway` and `HTTPRoute` resources for listener TLS, namespace isolation, host specificity, HTTPS redirects, cross-namespace authorization, and backend TLS. These checks work with conformant implementations such as kgateway.
`httpRouteBackendTLSMissing` also recognizes kgateway's `Backend` and `BackendConfigPolicy` resources. It identifies TLS backends from ports 443 and 8443, Service port names and `appProtocol`, and kgateway static Backend ports. `kgatewayBackendTLSVerificationDisabled` checks the kgateway-specific `insecureSkipVerify` setting. Authentication, authorization, and rate-limiting requirements are organization-specific and should be implemented as custom checks.
Checks marked "Cluster audits only" need related resources that are not available when Polaris evaluates a single admission request. They pass without a resource provider rather than rejecting an object without enough context.
A pod running with the `hostNetwork` attribute enabled will have access to the loopback device, services listening on localhost, and could be used to snoop on network activity of other pods on the same node. There are certain examples where setting `hostNetwork` to true is required, such as deploying a networking plugin like Flannel.
Setting the `hostPort` attribute on a container will ensure that it is accessible on that specific port on each node it is deployed to. Unfortunately when this is specified, it limits where a pod can actually be scheduled in a cluster.
-16
View File
@@ -9,8 +9,6 @@ meta:
# top-level commands
audit
Runs a one-time audit.
auth
Authenticate polaris with Fairwinds Insights
dashboard
Runs the webserver for Polaris dashboard.
fix
@@ -44,7 +42,6 @@ webhook
# audit flags
--audit-path string If specified, audits one or more YAML files instead of a cluster.
--checks strings Optional flag to specify specific checks to check
--cluster-name string Set --cluster-name to a descriptive name for the cluster you're auditing
--color Whether to use color in pretty format. (default true)
--display-name string An optional identifier for the audit.
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
@@ -56,13 +53,11 @@ webhook
--only-show-failed-tests If specified, audit output will only show failed tests.
--output-file string Destination file for audit results.
--output-url string Destination URL to send audit results.
--quiet Suppress the 'upload to Insights' prompt.
--resource string Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
--severity string Severity level used to filter results. Behaves like log levels. 'danger' is the least verbose (warning, danger)
--skip-ssl-validation Skip https certificate verification
--upload-insights Upload scan results to Fairwinds Insights
# fix flags
--checks strings Optional flag to specify specific checks to fix eg. checks=hostIPCSet,hostPIDSet and checks=all applies fix to all defined checks mutations
@@ -76,15 +71,4 @@ webhook
-h, --help help for webhook
-p, --port int Port for the dashboard webserver. (default 9876)
# auth sub-commands
login Authenticate polaris with Fairwinds Insights.
logout Log out of a Fairwinds Insights.
status View authentication status.
token Print the auth token gh is configured to use.
```
#### Suppressing 'upload to Insights' output
When running the `polaris audit` subcommand, you can suppress the following output using the `--quiet flag`:
> 🚀 Upload your Polaris findings to Fairwinds Insights to see remediation advice, add teammates, integrate with Slack or Jira, and more:
+3 -3
View File
@@ -46,11 +46,11 @@ go test ./pkg/... -v -coverprofile cover.out
### Webhook tests
```bash
kind create cluster --wait=90s --image kindest/node:v1.15.11 --name polaris-test
docker build -t quay.io/fairwinds/polaris:debug . # or use your own registry
docker push quay.io/fairwinds/polaris:debug
docker build -t us-docker.pkg.dev/fairwinds-ops/oss/polaris:debug . # or use your own registry
docker push us-docker.pkg.dev/fairwinds-ops/oss/polaris:debug
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.12.1 --set "installCRDs=true" --wait
POLARIS_IMAGE=quay.io/fairwinds/polaris:debug ./test/webhook_test.sh
POLARIS_IMAGE=us-docker.pkg.dev/fairwinds-ops/oss/polaris:debug ./test/webhook_test.sh
```
to avoid the final cleanup for debugging purposes, you can run
```bash
+5 -5
View File
@@ -25,7 +25,7 @@ customChecks:
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
image:
@@ -73,7 +73,7 @@ customChecks:
category: Resources
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- resources
@@ -120,7 +120,7 @@ successMessage: Label app.kubernetes.io/name matches metadata.name
failureMessage: Label app.kubernetes.io/name must match metadata.name
target: Controller
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
metadata:
@@ -193,7 +193,7 @@ controllers:
include:
- Deployment
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
metadata:
@@ -233,7 +233,7 @@ customChecks:
foo:
jsonSchema: |
{
"$schema": "http://json-schema.org/draft-07/schema",
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object"
}
```
+1 -1
View File
@@ -42,7 +42,7 @@ polaris dashboard --port 8080 --audit-path=./deploy/
### Local Docker container
```
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro quay.io/fairwinds/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro us-docker.pkg.dev/fairwinds-ops/oss/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
```
## Using the Dashboard
Executable
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
set -euo pipefail
KIND_VERSION=v0.30.0
if [ -z "${CI_SHA1:-}" ]; then
echo "CI_SHA1 not set"
exit 1
fi
echo "CI_SHA1: ${CI_SHA1}"
tar="/tmp/workspace/docker_save/polaris_${CI_SHA1}-amd64.tar"
if [ ! -f "$tar" ]; then
echo "Missing snapshot image at ${tar}"
exit 1
fi
if ! command -v kind > /dev/null; then
echo "Installing kind ${KIND_VERSION}"
bindir="$(pwd)/bin-kind"
mkdir -p "$bindir"
curl -fsSLo "$bindir/kind" \
"https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
chmod +x "$bindir/kind"
export PATH="$bindir:$PATH"
fi
kind version
docker load --input "$tar"
docker tag "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CI_SHA1}-amd64" \
"us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CI_SHA1}"
kind load docker-image --name e2e "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CI_SHA1}"
docker cp . e2e-command-runner:/polaris
Executable
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
set -euo pipefail
mkdir -p /tmp/test-results
if [[ -n "${CIRCLE_PR_NUMBER:-}" ]]; then
echo "Skipping Kubernetes tests for forked PR"
exit 0
fi
cd /polaris
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.12.1 \
--set installCRDs=true \
--wait \
--create-namespace
./test/webhook_test.sh
./test/kube_dashboard_test.sh
+1 -1
View File
@@ -4,4 +4,4 @@ options:
images:
docker:
- quay.io/fairwinds/polaris:$CI_SHA1
- us-docker.pkg.dev/fairwinds-ops/oss/polaris:$CI_SHA1
+60 -57
View File
@@ -1,83 +1,86 @@
module github.com/fairwindsops/polaris
go 1.22.7
go 1.26.2
require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/fairwindsops/controller-utils v0.3.4
github.com/fairwindsops/insights-plugins/plugins/workloads v0.0.0-20240917173116-506f92bdf9a0
github.com/fatih/color v1.17.0
github.com/fatih/color v1.19.0
github.com/gorilla/mux v1.8.1
github.com/pkg/errors v0.9.1
github.com/qri-io/jsonschema v0.1.2
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/qri-io/jsonpointer v0.1.1
github.com/qri-io/jsonschema v0.2.1
github.com/sirupsen/logrus v1.10.1
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.12.1
github.com/thoas/go-funk v0.9.3
gomodules.xyz/jsonpatch/v2 v2.4.0
gomodules.xyz/jsonpatch/v2 v2.5.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.31.1
k8s.io/apimachinery v0.31.1
k8s.io/client-go v0.31.1
sigs.k8s.io/controller-runtime v0.19.0
sigs.k8s.io/yaml v1.4.0
k8s.io/api v0.36.4
k8s.io/apimachinery v0.36.4
k8s.io/client-go v0.36.4
sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/yaml v1.6.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.3 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.20.3 // indirect
github.com/go-openapi/jsonreference v0.20.5 // indirect
github.com/go-openapi/swag v0.22.10 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/jsonreference v0.21.5 // indirect
github.com/go-openapi/swag v0.25.5 // indirect
github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/fileutils v0.25.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
github.com/go-openapi/swag/loading v0.25.5 // indirect
github.com/go-openapi/swag/mangling v0.25.5 // indirect
github.com/go-openapi/swag/netutils v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect
github.com/samber/lo v1.46.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/samber/lo v1.53.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.39.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
k8s.io/apiextensions-apiserver v0.36.0 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect
)
+148 -184
View File
@@ -1,256 +1,220 @@
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE=
github.com/emicklei/go-restful/v3 v3.11.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/fairwindsops/controller-utils v0.3.4 h1:t1qulL2GVDVUJTIE4icpBy3KnsxFTavnNAbFnd60blc=
github.com/fairwindsops/controller-utils v0.3.4/go.mod h1:9/hOHX70/LG40RgtFAjtXFiMWEpItqm6Scf+obRFB2Y=
github.com/fairwindsops/insights-plugins/plugins/workloads v0.0.0-20240917173116-506f92bdf9a0 h1:7adPvardRgDkZlEfZJwNzfT25bZ9KY2odhmZeud19ds=
github.com/fairwindsops/insights-plugins/plugins/workloads v0.0.0-20240917173116-506f92bdf9a0/go.mod h1:J+1LlUXLrbYbCzk/oxe9NLTToUWtCRzfM3akvOV+VxM=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk=
github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM=
github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI=
github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y=
github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA=
github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=
github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y=
github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/qri-io/jsonpointer v0.1.0/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
github.com/qri-io/jsonschema v0.1.2 h1:JlI7JAlxBbxh5Y641ctf+3kxcwYM6QbKDiqiQRkIBr0=
github.com/qri-io/jsonschema v0.1.2/go.mod h1:SiF7DGMMKfw3cPrKZErviQKaUGserd2PYMOGm0vrELU=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ=
github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q=
github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo=
k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw=
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug=
k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4=
k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk=
k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0=
k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw=
sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
-53
View File
@@ -1,53 +0,0 @@
package auth
import (
"errors"
"io"
"os"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
var userHomeDir string
var polarisHostsFilepath string
var ErrNotLoggedIn = errors.New("not logged in")
func init() {
var err error
userHomeDir, err = os.UserHomeDir()
if err != nil {
logrus.Fatalf("reading user home dir: %v", err)
}
polarisHostsFilepath = userHomeDir + "/.config/polaris/hosts.yaml"
}
func readPolarisHostsFile() (map[string]Host, error) {
f, err := os.Open(polarisHostsFilepath)
if err != nil {
return nil, err
}
b, err := io.ReadAll(f)
if err != nil {
return nil, err
}
content := map[string]Host{}
err = yaml.Unmarshal(b, &content)
return content, err
}
func GetAuth(insightsHost string) (*Host, error) {
hosts, err := readPolarisHostsFile()
if err != nil {
return nil, err
}
if len(hosts) == 0 {
return nil, ErrNotLoggedIn
}
if h, ok := hosts[insightsHost]; ok {
return &h, nil
}
return nil, ErrNotLoggedIn
}
-45
View File
@@ -1,45 +0,0 @@
package auth
import (
"errors"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/sirupsen/logrus"
)
// openBrowser opens up the provided URL in a browser
func openBrowser(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "openbsd":
fallthrough
case "linux":
cmd = exec.Command("xdg-open", url)
case "darwin":
cmd = exec.Command("open", url)
case "windows":
r := strings.NewReplacer("&", "^&")
cmd = exec.Command("cmd", "/c", "start", r.Replace(url))
}
if cmd != nil {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
logrus.Printf("Failed to open browser due to error %v", err)
return fmt.Errorf("Failed to open browser: " + err.Error())
}
err = cmd.Wait()
if err != nil {
logrus.Printf("Failed to wait for open browser command to finish due to error %v", err)
return fmt.Errorf("Failed to wait for open browser command to finish: " + err.Error())
}
return nil
} else {
return errors.New("unsupported platform")
}
}
-291
View File
@@ -1,291 +0,0 @@
package auth
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/fairwindsops/polaris/pkg/insights"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
const loginPath = "/auth/login"
const registerPath = "/auth/register"
const (
loginUsingBrowser = "Login with a web browser"
pasteAnAuthenticationToken = "Paste an authentication token"
)
type paramsOrError struct {
token string
user string
organization string
err error
}
type Host struct {
Token string `yaml:"token"`
User string `yaml:"user"`
Organization string `yaml:"organization"`
}
var paramsOrErrorChan = make(chan paramsOrError)
func HandleLogin(insightsHost string) error {
if _, err := os.Stat(polarisHostsFilepath); err == nil {
content, err := readPolarisHostsFile()
if err != nil {
return fmt.Errorf("reading polaris hosts file: %w", err)
}
if len(content) > 0 {
if h, ok := content[insightsHost]; ok {
c := insights.NewHTTPClient(insightsHost, h.Organization, h.Token)
isValid, err := c.IsTokenValid()
if err != nil {
return err
}
if isValid {
var reAuthenticate bool
err = survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("You're already logged into %s. Do you want to re-authenticate?", insightsHost)}, &reAuthenticate)
if err != nil {
return fmt.Errorf("prompting re-authenticate: %w", err)
}
if !reAuthenticate {
// bail-out
return nil
}
}
}
}
}
selection := &survey.Select{
Message: "How would you like to authenticate Polaris?",
Options: []string{loginUsingBrowser, pasteAnAuthenticationToken},
Default: loginUsingBrowser,
}
var answer string
err := survey.AskOne(selection, &answer)
if err != nil {
return fmt.Errorf("asking how to authenticate: %w", err)
}
var user, token, organization string
if answer == loginUsingBrowser {
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
panic(err)
}
localServerPort := listener.Addr().(*net.TCPAddr).Port
url := fmt.Sprintf(insightsHost + registerPath + "?source=polaris&callbackUrl=" + fmt.Sprintf("http://localhost:%d/auth/login/callback", localServerPort))
err = openBrowser(url)
if err != nil {
logrus.Warnf("could not open browser: %v", err)
logrus.Infoln("paste the link below into your browser:")
os.Stdout.Write([]byte(url + "\n"))
}
var router *mux.Router
go func() {
router = mux.NewRouter()
router.HandleFunc("/auth/login/callback", callbackHandler(insightsHost, localServerPort))
if err := http.Serve(listener, router); err != nil {
paramsOrErrorChan <- paramsOrError{err: fmt.Errorf("starting the local http server: %w", err)}
}
}()
// wait the browser to callback the local server
paramOrError := <-paramsOrErrorChan
if paramOrError.err != nil {
return paramOrError.err
}
user = paramOrError.user
organization = paramOrError.organization
token = paramOrError.token
} else {
var answer string
var bot bot
err := survey.AskOne(&survey.Password{Message: "Paste your authentication token:"}, &answer, survey.WithValidator(validateToken(insightsHost, &bot)))
if err != nil {
return fmt.Errorf("asking how to authenticate: %w", err)
}
token = answer
user = bot.Name
organization = bot.Organization
}
polarisCfgDir := filepath.Join(userHomeDir, ".config", "polaris")
err = os.MkdirAll(polarisCfgDir, os.ModePerm)
if err != nil {
return fmt.Errorf("creating polaris config dir: %w", err)
}
content := map[string]Host{insightsHost: {Token: token, User: user, Organization: organization}}
b, err := yaml.Marshal(content)
if err != nil {
return fmt.Errorf("marshalling yaml data: %w", err)
}
err = os.WriteFile(polarisHostsFilepath, b, os.ModePerm)
if err != nil {
return fmt.Errorf("writing data to file: %w", err)
}
logrus.Debugf("hosts file has been saved")
fmt.Println("✓ Authentication complete.")
fmt.Printf("✓ Logged in organization %s as %s.\n", organization, user)
return nil
}
func fetchAuthToken(insightsHost, organization, code string) (string, error) {
authTokenURL := fmt.Sprintf("%s/v0/organizations/%s/auth/token", insightsHost, organization)
body := map[string]any{"grantType": "authorization_code", "code": code}
b, err := json.Marshal(body)
if err != nil {
return "", err
}
r, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(b))
if err != nil {
return "", err
}
r.Header.Add("Content-Type", "application/json")
res, err := http.DefaultClient.Do(r)
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 400 {
return "", fmt.Errorf("expected 200 OK - received %s", res.Status)
}
var rBody map[string]any
err = json.NewDecoder(res.Body).Decode(&rBody)
if err != nil {
return "", err
}
token, ok := rBody["accessToken"].(string)
if !ok {
return "", fmt.Errorf("unable to parse accessToken from response body: %v", rBody)
}
return token, nil
}
func callbackHandler(insightsHost string, localServerPort int) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// checks for error in the params
errMsg := r.URL.Query().Get("error")
if len(errMsg) > 0 {
errDescriptionMsg := r.URL.Query().Get("error_description")
fmt.Fprintf(w, "unable to perform integration: %s - %s", errMsg, errDescriptionMsg)
paramsOrErrorChan <- paramsOrError{err: fmt.Errorf("%s - %s", errMsg, errDescriptionMsg)}
return
}
var err error
code := r.URL.Query().Get("code")
if len(code) == 0 {
err = errors.New("code query param is required in callback")
}
user := r.URL.Query().Get("user")
if len(user) == 0 {
err = errors.New("user query param is required in callback")
}
organization := r.URL.Query().Get("organization")
if len(organization) == 0 {
err = errors.New("organization query param is required in callback")
}
token, err := fetchAuthToken(insightsHost, organization, code)
if err != nil {
err = fmt.Errorf("fetching auth token: %w", err)
}
if err != nil {
fmt.Fprintf(w, "unable to perform integration: %v", err)
paramsOrErrorChan <- paramsOrError{err: err}
return
}
fmt.Fprint(w, "Polaris and Fairwinds Insights integration has finished successfully, your credentials are set! You can safely close this tab now.")
paramsOrErrorChan <- paramsOrError{token: token, user: user, organization: organization}
return
}
}
func validateToken(insightsHost string, bot *bot) func(args any) error {
return func(args any) error {
token, ok := args.(string)
if !ok {
return errors.New("casting token to string")
}
if len(strings.TrimSpace(token)) <= 0 {
return errors.New("token is required")
}
return fetchOrganizationBot(insightsHost, token, bot)
}
}
type bot struct {
ID int
Organization string
Name string
Role string
AuthToken string
CreatedAt time.Time
}
func fetchOrganizationBot(insightsHost, authToken string, bot *bot) error {
authTokenURL := fmt.Sprintf("%s/v0/bots/from-request", insightsHost)
r, err := http.NewRequest("GET", authTokenURL, nil)
if err != nil {
return err
}
r.Header.Add("Content-Type", "application/json")
r.Header.Add("Authorization", "Bearer "+authToken)
res, err := http.DefaultClient.Do(r)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 400 {
return fmt.Errorf("expected 200 (OK) - received %d", res.StatusCode)
}
err = json.NewDecoder(res.Body).Decode(bot)
if err != nil {
return err
}
return nil
}
func IsLoggedIn() bool {
if _, err := os.Stat(polarisHostsFilepath); err == nil {
content, err := readPolarisHostsFile()
if err != nil {
return false
}
return len(content) > 0
}
return false
}
-35
View File
@@ -1,35 +0,0 @@
package auth
import (
"fmt"
"os"
)
func HandleLogout() error {
if !IsLoggedIn() {
fmt.Println("not logged in to Fairwinds Insights")
return nil
}
err := performLogout()
if err != nil {
return fmt.Errorf("performing logout: %v", err)
}
fmt.Println("✓ Logged out of Fairwinds Insights")
return nil
}
func performLogout() error {
content, err := readPolarisHostsFile()
if err != nil {
return nil
}
if len(content) > 0 {
err = os.WriteFile(polarisHostsFilepath, []byte("{}"), os.ModePerm)
if err != nil {
return fmt.Errorf("writing data to file: %w", err)
}
return nil
}
return nil
}
-46
View File
@@ -1,46 +0,0 @@
package auth
import (
"fmt"
"strings"
"github.com/fairwindsops/polaris/pkg/insights"
)
func PrintStatus(insightsHost string) error {
if content, err := readPolarisHostsFile(); err == nil {
if len(content) > 0 {
if h, ok := content[insightsHost]; ok {
c := insights.NewHTTPClient(insightsHost, h.Organization, h.Token)
isValid, err := c.IsTokenValid()
if err != nil {
return err
}
if !isValid {
fmt.Println("✕ Your token is no longer valid. Run polaris auth login to authenticate.")
return nil
}
fmt.Printf("✓ Logged in to %s as %s\n", insightsHost, h.User)
fmt.Printf("✓ Token: %s\n", hideToken(h.Token, 3))
return nil
}
}
fmt.Printf("✕ No authentication found for host %s. Run polaris auth login to authenticate.\n", insightsHost)
return nil
}
fmt.Println("You are not logged into Fairwinds Insights. Run polaris auth login to authenticate.")
return nil
}
func hideToken(token string, hideAfter int) string {
var i int
return strings.Map(func(r rune) rune {
defer func() {
i++
}()
if i > hideAfter {
return []rune("*")[0]
}
return r
}, token)
}
-20
View File
@@ -1,20 +0,0 @@
package auth
import "fmt"
func PrintToken() error {
if content, err := readPolarisHostsFile(); err == nil {
if len(content) > 0 {
for k, h := range content {
if len(content) == 1 {
fmt.Println(h.Token)
} else {
fmt.Printf("%s: %s\n", k, h.Token)
}
}
return nil
}
}
fmt.Println("no oauth token")
return nil
}
+12
View File
@@ -29,11 +29,14 @@ var (
"deploymentMissingReplicas",
// Pod checks
"hostIPCSet",
"hostPathSet",
"hostProcess",
"hostPIDSet",
"hostNetworkSet",
"automountServiceAccountToken",
"topologySpreadConstraint",
// Container checks
"procMount",
"memoryLimitsMissing",
"memoryRequestsMissing",
"cpuLimitsMissing",
@@ -54,6 +57,15 @@ var (
"sensitiveContainerEnvVar",
// Other checks
"tlsSettingsMissing",
"gatewayTLSMissing",
"gatewayAllowedRoutesAll",
"gatewayInsecureFrontendValidation",
"gatewayCrossNamespaceCertificateRef",
"httpRouteWildcardOrEmptyHost",
"httpRouteInsecureListener",
"httpRouteCrossNamespaceBackendRef",
"httpRouteBackendTLSMissing",
"kgatewayBackendTLSVerificationDisabled",
"pdbDisruptionsIsZero",
"metadataAndInstanceMismatched",
"missingPodDisruptionBudget",
@@ -3,7 +3,7 @@ failureMessage: The ServiceAccount will be automounted
category: Security
target: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
serviceAccountName:
@@ -3,7 +3,7 @@ failureMessage: The ClusterRole allows Pods/exec or pods/attach
category: Security
target: rbac.authorization.k8s.io/ClusterRole
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required: ["metadata", "rules"]
anyOf:
@@ -3,7 +3,7 @@ failureMessage: The ClusterRoleBinding references the default cluster-admin Clus
category: Security
target: rbac.authorization.k8s.io/ClusterRoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
anyOf:
# Do not alert on default ClusterRoleBindings.
@@ -3,7 +3,7 @@ failureMessage: The ClusterRoleBinding references a ClusterRole that allows Pods
category: Security
target: rbac.authorization.k8s.io/ClusterRoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
anyOf:
# Do not alert on default ClusterRoleBindings.
+1 -1
View File
@@ -6,7 +6,7 @@ containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- resources
+1 -1
View File
@@ -6,7 +6,7 @@ containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- resources
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Container should not have dangerous capabilities
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
securityContext:
@@ -6,7 +6,7 @@ controllers:
include:
- Deployment
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- spec
@@ -0,0 +1,25 @@
successMessage: Gateway listeners restrict route attachment by namespace
failureMessage: Gateway listeners should not allow routes from all namespaces
category: Security
target: gateway.networking.k8s.io/Gateway
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
spec:
type: object
properties:
listeners:
type: array
items:
type: object
properties:
allowedRoutes:
type: object
properties:
namespaces:
type: object
properties:
from:
not:
const: All
@@ -0,0 +1,6 @@
successMessage: Gateway cross-namespace certificate references are authorized
failureMessage: Gateway cross-namespace certificate references should have a matching ReferenceGrant
category: Security
target: gateway.networking.k8s.io/Gateway
relatedKinds:
- gateway.networking.k8s.io/ReferenceGrant
@@ -0,0 +1,40 @@
successMessage: Gateway frontend client certificate validation fails closed
failureMessage: Gateway frontend client certificate validation should not allow insecure fallback
category: Security
target: gateway.networking.k8s.io/Gateway
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
spec:
type: object
properties:
tls:
type: object
properties:
frontend:
type: object
properties:
default:
type: object
properties:
validation:
type: object
properties:
mode:
not:
const: AllowInsecureFallback
perPort:
type: array
items:
type: object
properties:
tls:
type: object
properties:
validation:
type: object
properties:
mode:
not:
const: AllowInsecureFallback
+41
View File
@@ -0,0 +1,41 @@
successMessage: Gateway TLS listeners have certificates configured
failureMessage: Gateway HTTPS, GRPC, and terminating TLS listeners should configure certificateRefs
category: Security
target: gateway.networking.k8s.io/Gateway
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
spec:
type: object
properties:
listeners:
type: array
items:
type: object
if:
anyOf:
- required: [protocol]
properties:
protocol:
enum: [HTTPS, GRPC]
- required: [protocol]
properties:
protocol:
const: TLS
tls:
type: object
properties:
mode:
not:
const: Passthrough
then:
required: [tls]
properties:
tls:
type: object
required: [certificateRefs]
properties:
certificateRefs:
type: array
minItems: 1
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Host IPC should not be configured
category: Security
target: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
hostIPC:
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Host network should not be configured
category: Security
target: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
hostNetwork:
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Host PID should not be configured
category: Security
target: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
hostPID:
+16
View File
@@ -0,0 +1,16 @@
successMessage: HostPath volumes are not configured
failureMessage: HostPath volumes must be forbidden
category: Security
target: PodSpec
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
volumes:
type: array
items:
type: object
properties:
hostPath:
type: string
const: ''
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Host port should not be configured
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
properties:
+31
View File
@@ -0,0 +1,31 @@
successMessage: Privileged access to the host check is valid
failureMessage: Privileged access to the host is disallowed
category: Security
target: PodSpec
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
containers:
type: array
items:
type: object
properties:
securityContext:
type: object
properties:
windowsOptions:
type: object
properties:
hostProcess:
type: boolean
const: false
securityContext:
type: object
properties:
windowsOptions:
type: object
properties:
hostProcess:
type: boolean
const: false
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: HPA maxReplicas and minReplicas should be different
category: Reliability
target: autoscaling/HorizontalPodAutoscaler
schemaString: |
"$schema": http://json-schema.org/draft-07/schema#
"$schema": https://json-schema.org/draft/2019-09/schema#
type: object
properties:
spec:
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: HPA minReplicas should be 2 or more
category: Reliability
target: autoscaling/HorizontalPodAutoscaler
schema:
"$schema": http://json-schema.org/draft-07/schema#
"$schema": https://json-schema.org/draft/2019-09/schema#
type: object
properties:
spec:
@@ -0,0 +1,9 @@
successMessage: HTTPRoute TLS backends have TLS origination configured
failureMessage: HTTPRoute backends on TLS ports should have a BackendTLSPolicy or kgateway BackendConfigPolicy
category: Security
target: gateway.networking.k8s.io/HTTPRoute
relatedKinds:
- Service
- gateway.networking.k8s.io/BackendTLSPolicy
- gateway.kgateway.dev/Backend
- gateway.kgateway.dev/BackendConfigPolicy
@@ -0,0 +1,6 @@
successMessage: HTTPRoute cross-namespace backend references are authorized
failureMessage: HTTPRoute cross-namespace backend references should have a matching ReferenceGrant
category: Security
target: gateway.networking.k8s.io/HTTPRoute
relatedKinds:
- gateway.networking.k8s.io/ReferenceGrant
@@ -0,0 +1,6 @@
successMessage: HTTPRoute uses secure listeners or redirects HTTP to HTTPS
failureMessage: HTTPRoute should not serve application traffic over an HTTP listener
category: Security
target: gateway.networking.k8s.io/HTTPRoute
relatedKinds:
- gateway.networking.k8s.io/Gateway
@@ -0,0 +1,19 @@
successMessage: HTTPRoute uses explicit hostnames
failureMessage: HTTPRoute should use explicit hostnames instead of matching every hostname
category: Security
target: gateway.networking.k8s.io/HTTPRoute
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required: [spec]
properties:
spec:
type: object
required: [hostnames]
properties:
hostnames:
type: array
minItems: 1
items:
type: string
pattern: '^[^*]+$'
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Container should not have insecure capabilities
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- securityContext
@@ -0,0 +1,17 @@
successMessage: kgateway backend TLS certificate verification is enabled
failureMessage: kgateway BackendConfigPolicy should not disable TLS certificate verification
category: Security
target: gateway.kgateway.dev/BackendConfigPolicy
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
spec:
type: object
properties:
tls:
type: object
properties:
insecureSkipVerify:
not:
const: true
+5 -5
View File
@@ -3,8 +3,8 @@ FailureMessage: Use one of AppArmor, Seccomp, SELinux, or dropping Linux Capabil
category: Security
target: Container
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
definitions:
'$schema': https://json-schema.org/draft/2019-09/schema
$defs:
podOrContainerSeccompProfile:
type: object
{{ $podSeccompProfileType := .Polaris.PodSpec.securityContext.seccompProfile.type }}
@@ -83,7 +83,7 @@ schemaString: |
type: object
{{ else }}
anyOf:
- $ref: "#/definitions/podOrContainerSeccompProfile"
- $ref: "#/definitions/podOrContainerSELinuxOptions"
- $ref: "#/definitions/containerDropCapabilities"
- $ref: "#/$defs/podOrContainerSeccompProfile"
- $ref: "#/$defs/podOrContainerSELinuxOptions"
- $ref: "#/$defs/containerDropCapabilities"
{{ end}}
+1 -1
View File
@@ -10,7 +10,7 @@ containers:
- initContainer
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- livenessProbe
+1 -1
View File
@@ -6,7 +6,7 @@ containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- resources
+1 -1
View File
@@ -6,7 +6,7 @@ containers:
exclude:
- initContainer
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- resources
@@ -3,7 +3,7 @@ failureMessage: Label app.kubernetes.io/instance must match metadata.name
category: Reliability
target: Controller
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
metadata:
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: A NetworkPolicy should match pod labels and contain applied egre
category: Security
target: PodTemplate
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
metadata:
@@ -6,7 +6,7 @@ controllers:
include:
- Deployment
schema:
"$schema": http://json-schema.org/draft-07/schema#
"$schema": https://json-schema.org/draft/2019-09/schema#
type: object
required: [spec]
properties:
@@ -4,8 +4,8 @@ category: Security
target: Container
schemaTarget: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
'$schema': https://json-schema.org/draft/2019-09/schema
$defs:
goodSecurityContext:
type: object
anyOf:
@@ -25,13 +25,13 @@ schema:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
$ref: "#/$defs/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
$ref: "#/$defs/notBadSecurityContext"
- properties:
containers:
type: array
@@ -40,7 +40,7 @@ schema:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
$ref: "#/$defs/goodSecurityContext"
mutations:
- op: add
path: /securityContext/readOnlyRootFilesystem
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Voluntary evictions are not possible
category: Reliability
target: policy/PodDisruptionBudget
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- spec
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Priority class should be set
category: Reliability
target: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- priorityClassName
@@ -4,8 +4,8 @@ category: Security
target: Container
schemaTarget: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
'$schema': https://json-schema.org/draft/2019-09/schema
$defs:
goodSecurityContext:
type: object
anyOf:
@@ -25,13 +25,13 @@ schema:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
$ref: "#/$defs/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
$ref: "#/$defs/notBadSecurityContext"
- properties:
containers:
type: array
@@ -40,7 +40,7 @@ schema:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
$ref: "#/$defs/goodSecurityContext"
mutations:
- op: add
+19
View File
@@ -0,0 +1,19 @@
successMessage: The default /proc masks are set up to reduce attack surface, and should be required
failureMessage: Proc mount must not be changed from the default
category: Security
target: PodSpec
schema:
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
containers:
type: array
items:
type: object
properties:
securityContext:
type: object
properties:
procMount:
type: string
const: Default
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Image pull policy should be "Always"
category: Reliability
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
required:
- imagePullPolicy
properties:
+1 -1
View File
@@ -10,7 +10,7 @@ containers:
- initContainer
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- readinessProbe
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: The Role allows Pods/exec or pods/attach
category: Security
target: rbac.authorization.k8s.io/Role
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required: ["metadata", "rules"]
anyOf:
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references the default cluster-admin ClusterRole
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
anyOf:
# Pass RoleBindings that point to a Role.
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a Role with wildcard permissions
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
anyOf:
# Pass RoleBindings that point to a ClusterRole.
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a ClusterRole that allows Pods/exec,
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
anyOf:
# Pass RoleBindings that point to a Role.
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a Role that allows Pods/exec, allows
category: Security
target: rbac.authorization.k8s.io/RoleBinding
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
anyOf:
# Pass RoleBindings that point to a ClusterRole.
@@ -17,7 +17,7 @@ schemaString: |
const: "rbac.authorization.k8s.io"
kind:
type: string
const: "Role"
const: "ClusterRole"
# Do not alert on default RoleBindings.
- required: ["metadata"]
properties:
+4 -4
View File
@@ -4,8 +4,8 @@ category: Security
target: Container
schemaTarget: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
'$schema': https://json-schema.org/draft/2019-09/schema
$defs:
notBadSecurityContext:
type: object
properties:
@@ -15,13 +15,13 @@ schema:
type: object
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
$ref: "#/$defs/notBadSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
$ref: "#/$defs/notBadSecurityContext"
mutations:
- op: add
path: /securityContext/privileged
+5 -5
View File
@@ -4,8 +4,8 @@ category: Security
target: Container
schemaTarget: PodSpec
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
'$schema': https://json-schema.org/draft/2019-09/schema
$defs:
goodSecurityContext:
type: object
anyOf:
@@ -33,13 +33,13 @@ schema:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
$ref: "#/$defs/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
$ref: "#/$defs/notBadSecurityContext"
# non-root specified at container level
- properties:
containers:
@@ -49,7 +49,7 @@ schema:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
$ref: "#/$defs/goodSecurityContext"
mutations:
- op: add
path: /securityContext/runAsNonRoot
@@ -3,7 +3,7 @@ failureMessage: Potentially sensitive content is detected in the ConfigMap keys
category: Security
target: /ConfigMap
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required: ["metadata"]
properties:
@@ -3,7 +3,7 @@ failureMessage: The container sets potentially sensitive environment variables
category: Security
target: Container
schemaString: |
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
env:
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Image tag should be specified
category: Reliability
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
required:
- image
allOf:
+1 -1
View File
@@ -3,7 +3,7 @@ failureMessage: Ingress does not have TLS configured
category: Security
target: networking.k8s.io/Ingress
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- spec
@@ -7,7 +7,7 @@ controllers:
- Job
- CronJob
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- topologySpreadConstraints
+42 -19
View File
@@ -52,29 +52,52 @@ type Exemption struct {
//go:embed default.yaml
var defaultConfig []byte
// ParseFile parses config from a file.
func ParseFile(path string) (Configuration, error) {
var rawBytes []byte
var err error
if path == "" {
rawBytes = defaultConfig
} else if strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://") {
// path is a url
response, err2 := http.Get(path)
if err2 != nil {
return Configuration{}, err2
}
rawBytes, err = io.ReadAll(response.Body)
} else {
// path is local
rawBytes, err = os.ReadFile(path)
}
// MergeConfigAndParseFile parses config from a file.
func MergeConfigAndParseFile(customConfigPath string, mergeConfig bool) (Configuration, error) {
rawBytes, err := mergeConfigFile(customConfigPath, mergeConfig)
if err != nil {
return Configuration{}, err
}
return Parse(rawBytes)
}
func mergeConfigFile(customConfigPath string, mergeConfig bool) ([]byte, error) {
if customConfigPath == "" {
return defaultConfig, nil
}
var customConfigContent []byte
var err error
if strings.HasPrefix(customConfigPath, "https://") || strings.HasPrefix(customConfigPath, "http://") {
// path is a url
response, err := http.Get(customConfigPath)
if err != nil {
return nil, err
}
customConfigContent, err = io.ReadAll(response.Body)
if err != nil {
return nil, err
}
} else {
// path is local
customConfigContent, err = os.ReadFile(customConfigPath)
if err != nil {
return nil, err
}
}
if mergeConfig {
mergedConfig, err := mergeYaml(defaultConfig, customConfigContent)
if err != nil {
return nil, err
}
return mergedConfig, nil
}
return customConfigContent, nil
}
// Parse parses config from a byte array.
func Parse(rawBytes []byte) (Configuration, error) {
reader := bytes.NewReader(rawBytes)
@@ -85,7 +108,7 @@ func Parse(rawBytes []byte) (Configuration, error) {
if err == io.EOF {
break
}
return conf, fmt.Errorf("Decoding config failed: %v", err)
return conf, fmt.Errorf("decoding config failed: %v", err)
}
}
for key, check := range conf.CustomChecks {
@@ -104,7 +127,7 @@ func Parse(rawBytes []byte) (Configuration, error) {
// Validate checks if a config is valid
func (conf Configuration) Validate() error {
if len(conf.Checks) == 0 {
return errors.New("No checks were enabled")
return errors.New("no checks were enabled")
}
return nil
}
+19 -16
View File
@@ -52,7 +52,7 @@ customChecks:
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- securityContext
@@ -69,7 +69,7 @@ customChecks:
target: Container
jsonSchema: >
{
"$schema": "http://json-schema.org/draft-07/schema",
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"required": ["securityContext"]
}
@@ -83,7 +83,7 @@ customChecks:
category: Security
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- securityContext
@@ -92,7 +92,7 @@ customChecks:
func TestParseError(t *testing.T) {
_, 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"
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)
}
@@ -115,7 +115,9 @@ func TestConfigFromURL(t *testing.T) {
var parsedConf Configuration
srv := &http.Server{Addr: ":8081"}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, confValidYAML)
if _, err := io.WriteString(w, confValidYAML); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
go func() {
@@ -125,7 +127,7 @@ func TestConfigFromURL(t *testing.T) {
}()
time.Sleep(time.Second)
parsedConf, err = ParseFile("http://localhost:8081/exampleURL")
parsedConf, err = MergeConfigAndParseFile("http://localhost:8081/exampleURL", false)
assert.NoError(t, err, "Expected no error when parsing YAML from URL")
if err := srv.Shutdown(context.TODO()); err != nil {
panic(err)
@@ -136,41 +138,42 @@ func TestConfigFromURL(t *testing.T) {
func TestConfigNoServerError(t *testing.T) {
var err error
_, err = ParseFile("http://localhost:8081/exampleURL")
_, err = MergeConfigAndParseFile("http://localhost:8081/exampleURL", false)
assert.Error(t, err)
assert.Regexp(t, regexp.MustCompile("connection refused"), err.Error())
}
func TestConfigWithCustomChecks(t *testing.T) {
valid := map[string]interface{}{
"securityContext": map[string]interface{}{
valid := map[string]any{
"securityContext": map[string]any{
"foo": "bar",
},
}
invalid := map[string]interface{}{
"notSecurityContext": map[string]interface{}{},
invalid := map[string]any{
"notSecurityContext": map[string]any{},
}
parsedConf, err := Parse([]byte(confCustomChecks))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]interface{}{})
isValid, _, err := check.CheckObject(valid)
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]any{})
assert.NoError(t, err)
isValid, _, err := check.CheckObject(context.TODO(), valid)
assert.NoError(t, err)
assert.Equal(t, true, isValid)
isValid, _, err = check.CheckObject(invalid)
isValid, _, err = check.CheckObject(context.TODO(), invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(context.TODO(), valid)
assert.NoError(t, err)
if !assert.Equal(t, true, isValid) {
fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
}
isValid, _, err = check.CheckObject(invalid)
isValid, _, err = check.CheckObject(context.TODO(), invalid)
assert.NoError(t, err)
assert.Equal(t, false, isValid)
}
+12
View File
@@ -23,11 +23,14 @@ checks:
# security
automountServiceAccountToken: warning
hostIPCSet: danger
hostPathSet: warning
hostProcess: warning
hostPIDSet: danger
linuxHardening: warning
missingNetworkPolicy: warning
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
procMount: warning
runAsRootAllowed: danger
runAsPrivileged: danger
dangerousCapabilities: danger
@@ -35,6 +38,15 @@ checks:
hostNetworkSet: danger
hostPortSet: warning
tlsSettingsMissing: warning
gatewayTLSMissing: warning
gatewayAllowedRoutesAll: warning
gatewayInsecureFrontendValidation: warning
gatewayCrossNamespaceCertificateRef: warning
httpRouteWildcardOrEmptyHost: warning
httpRouteInsecureListener: warning
httpRouteCrossNamespaceBackendRef: warning
httpRouteBackendTLSMissing: warning
kgatewayBackendTLSVerificationDisabled: warning
sensitiveContainerEnvVar: danger
sensitiveConfigmapContent: danger
clusterrolePodExecAttach: danger
+14 -2
View File
@@ -23,11 +23,14 @@ checks:
# security
automountServiceAccountToken: warning
hostIPCSet: danger
hostPathSet: warning
hostProcess: warning
hostPIDSet: danger
linuxHardening: danger
missingNetworkPolicy: warning
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
procMount: warning
runAsRootAllowed: danger
runAsPrivileged: danger
dangerousCapabilities: danger
@@ -35,6 +38,15 @@ checks:
hostNetworkSet: danger
hostPortSet: warning
tlsSettingsMissing: warning
gatewayTLSMissing: warning
gatewayAllowedRoutesAll: warning
gatewayInsecureFrontendValidation: warning
gatewayCrossNamespaceCertificateRef: warning
httpRouteWildcardOrEmptyHost: warning
httpRouteInsecureListener: warning
httpRouteCrossNamespaceBackendRef: warning
httpRouteBackendTLSMissing: warning
kgatewayBackendTLSVerificationDisabled: warning
sensitiveContainerEnvVar: danger
sensitiveConfigmapContent: danger
clusterrolePodExecAttach: danger
@@ -72,7 +84,7 @@ customChecks:
category: Resources
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
required:
- resources
@@ -102,7 +114,7 @@ customChecks:
category: Images
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
'$schema': https://json-schema.org/draft/2019-09/schema
type: object
properties:
image:
+45
View File
@@ -0,0 +1,45 @@
package config
import (
"gopkg.in/yaml.v3" // do not change the yaml import
)
func mergeYaml(defaultConfig, overridesConfig []byte) ([]byte, error) {
var defaultData, overrideConfig map[string]any
err := yaml.Unmarshal([]byte(defaultConfig), &defaultData)
if err != nil {
return nil, err
}
err = yaml.Unmarshal([]byte(overridesConfig), &overrideConfig)
if err != nil {
return nil, err
}
mergedData := mergeYAMLMaps(defaultData, overrideConfig)
mergedConfig, err := yaml.Marshal(mergedData)
if err != nil {
return nil, err
}
return mergedConfig, nil
}
func mergeYAMLMaps(defaults, overrides map[string]any) map[string]any {
for k, v := range overrides {
if vMap, ok := v.(map[string]any); ok {
// if the key exists in defaults and is a map, recursively merge
if mv1, ok := defaults[k].(map[string]any); ok {
defaults[k] = mergeYAMLMaps(mv1, vMap)
} else {
defaults[k] = vMap
}
} else {
// add or overwrite the value in defaults
defaults[k] = v
}
}
return defaults
}
+50
View File
@@ -0,0 +1,50 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
var defaults = `
checks:
deploymentMissingReplicas: warning
priorityClassNotSet: warning
tagNotSpecified: danger
existing:
sub:
key: value
`
var overrides = `
checks:
pullPolicyNotAlways: ignore
tagNotSpecified: overrides
existing:
sub:
key1: value1
new: value
new:
key: value
`
func TestMergeYaml(t *testing.T) {
mergedContent, err := mergeYaml([]byte(defaults), []byte(overrides))
assert.NoError(t, err)
expectedYAML := `checks:
deploymentMissingReplicas: warning
priorityClassNotSet: warning
pullPolicyNotAlways: ignore
tagNotSpecified: overrides
existing:
new: value
sub:
key: value
key1: value1
new:
key: value
`
assert.Equal(t, expectedYAML, string(mergedContent))
}
+89 -70
View File
@@ -16,13 +16,17 @@ package config
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"maps"
"slices"
"strings"
"text/template"
"github.com/qri-io/jsonpointer"
"github.com/qri-io/jsonschema"
"github.com/thoas/go-funk"
corev1 "k8s.io/api/core/v1"
@@ -56,34 +60,35 @@ var HandledTargets = []TargetKind{
type Mutation struct {
Path string
Op string
Value interface{}
Value any
Comment string
}
// SchemaCheck is a Polaris check that runs using JSON Schema
type SchemaCheck struct {
ID string `yaml:"id" json:"id"`
Category string `yaml:"category" json:"category"`
SuccessMessage string `yaml:"successMessage" json:"successMessage"`
FailureMessage string `yaml:"failureMessage" json:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers" json:"controllers"`
Containers includeExcludeList `yaml:"containers" json:"containers"`
Target TargetKind `yaml:"target" json:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget" json:"schemaTarget"`
Schema map[string]interface{} `yaml:"schema" json:"schema"`
SchemaString string `yaml:"schemaString" json:"schemaString"`
Validator jsonschema.RootSchema `yaml:"-" json:"-"`
AdditionalSchemas map[string]map[string]interface{} `yaml:"additionalSchemas" json:"additionalSchemas"`
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
AdditionalValidators map[string]jsonschema.RootSchema `yaml:"-" json:"-"`
Mutations []Mutation `yaml:"mutations" json:"mutations"`
ID string `yaml:"id" json:"id"`
Category string `yaml:"category" json:"category"`
SuccessMessage string `yaml:"successMessage" json:"successMessage"`
FailureMessage string `yaml:"failureMessage" json:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers" json:"controllers"`
Containers includeExcludeList `yaml:"containers" json:"containers"`
Target TargetKind `yaml:"target" json:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget" json:"schemaTarget"`
Schema map[string]any `yaml:"schema" json:"schema"`
SchemaString string `yaml:"schemaString" json:"schemaString"`
Validator jsonschema.Schema `yaml:"-" json:"-"`
AdditionalSchemas map[string]map[string]any `yaml:"additionalSchemas" json:"additionalSchemas"`
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
AdditionalValidators map[string]jsonschema.Schema `yaml:"-" json:"-"`
RelatedKinds []TargetKind `yaml:"relatedKinds" json:"relatedKinds"`
Mutations []Mutation `yaml:"mutations" json:"mutations"`
}
type resourceMinimum string
type resourceMaximum string
// UnmarshalYAMLOrJSON is a helper function to unmarshal data in an arbitrary format
func UnmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
func UnmarshalYAMLOrJSON(raw []byte, dest any) error {
reader := bytes.NewReader(raw)
d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
@@ -91,7 +96,7 @@ func UnmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
if err == io.EOF {
break
}
return fmt.Errorf("Decoding schema check failed: %v", err)
return fmt.Errorf("decoding schema check failed: %v", err)
}
}
return nil
@@ -104,13 +109,16 @@ func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
if err != nil {
return check, err
}
check.Initialize(id)
if err := check.Initialize(id); err != nil {
return check, err
}
return check, nil
}
func init() {
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
jsonschema.RegisterKeyword("resourceMinimum", newResourceMinimum)
jsonschema.RegisterKeyword("resourceMaximum", newResourceMaximum)
jsonschema.LoadDraft2019_09()
}
type includeExcludeList struct {
@@ -118,50 +126,68 @@ type includeExcludeList struct {
Exclude []string `yaml:"exclude"`
}
func newResourceMinimum() jsonschema.Validator {
func newResourceMinimum() jsonschema.Keyword {
return new(resourceMinimum)
}
func newResourceMaximum() jsonschema.Validator {
func newResourceMaximum() jsonschema.Keyword {
return new(resourceMaximum)
}
// Validate checks that a specified quanitity is not less than the minimum
func (min resourceMinimum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
err := validateRange(path, string(min), data, true)
func (min resourceMinimum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data any) {
err := validateRange(string(min), data, true)
if err != nil {
errs := currentState.Errs
*errs = append(*errs, *err...)
currentState.Errs = errs
}
}
func (max resourceMaximum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data any) {
err := validateRange(string(max), data, false)
if err != nil {
errs := currentState.Errs
*errs = append(*errs, *err...)
currentState.Errs = errs
}
}
// Validate checks that a specified quanitity is not greater than the maximum
func (max resourceMaximum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
err := validateRange(path, string(max), data, false)
if err != nil {
*errs = append(*errs, *err...)
}
func (min resourceMinimum) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema {
// Not implemented
return nil
}
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.ValError) {
func (min resourceMinimum) Register(uri string, registry *jsonschema.SchemaRegistry) {
// Not implemented
}
func (max resourceMaximum) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema {
// Not implemented
return nil
}
func (max resourceMaximum) Register(uri string, registry *jsonschema.SchemaRegistry) {
// Not implemented
}
func parseQuantity(i any) (resource.Quantity, *[]jsonschema.KeyError) {
if resNum, ok := i.(float64); ok {
i = fmt.Sprintf("%f", resNum)
}
resStr, ok := i.(string)
if !ok {
return resource.Quantity{}, &[]jsonschema.ValError{
return resource.Quantity{}, &[]jsonschema.KeyError{
{Message: fmt.Sprintf("Resource quantity %v is not a string", i)},
}
}
q, err := resource.ParseQuantity(resStr)
if err != nil {
return resource.Quantity{}, &[]jsonschema.ValError{
return resource.Quantity{}, &[]jsonschema.KeyError{
{Message: fmt.Sprintf("Could not parse resource quantity: %s", resStr)},
}
}
return q, nil
}
func validateRange(path string, limit interface{}, data interface{}, isMinimum bool) *[]jsonschema.ValError {
func validateRange(limit any, data any, isMinimum bool) *[]jsonschema.KeyError {
limitQuantity, err := parseQuantity(limit)
if err != nil {
return err
@@ -173,14 +199,14 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
cmp := limitQuantity.Cmp(actualQuantity)
if isMinimum {
if cmp == 1 {
return &[]jsonschema.ValError{
{Message: fmt.Sprintf("%s quantity %v is > %v", path, actualQuantity, limitQuantity)},
return &[]jsonschema.KeyError{
{Message: fmt.Sprintf("quantity %v is > %v", actualQuantity, limitQuantity)},
}
}
} else {
if cmp == -1 {
return &[]jsonschema.ValError{
{Message: fmt.Sprintf("%s quantity %v is < %v", path, actualQuantity, limitQuantity)},
return &[]jsonschema.KeyError{
{Message: fmt.Sprintf("quantity %v is < %v", actualQuantity, limitQuantity)},
}
}
}
@@ -207,21 +233,19 @@ func (check *SchemaCheck) Initialize(id string) error {
}
check.AdditionalSchemaStrings[kind] = string(jsonBytes)
}
check.Schema = map[string]interface{}{}
check.AdditionalSchemas = map[string]map[string]interface{}{}
check.Schema = map[string]any{}
check.AdditionalSchemas = map[string]map[string]any{}
return nil
}
// TemplateForResource fills out a check's templated fields given a particular resource
func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, error) {
func (check SchemaCheck) TemplateForResource(res any) (*SchemaCheck, error) {
newCheck := check // Make a copy of the check, since we're going to modify the schema
templateStrings := map[string]string{
"": newCheck.SchemaString,
}
for kind, schema := range newCheck.AdditionalSchemaStrings {
templateStrings[kind] = schema
}
maps.Copy(templateStrings, newCheck.AdditionalSchemaStrings)
newCheck.SchemaString = ""
newCheck.AdditionalSchemaStrings = map[string]string{}
@@ -251,9 +275,9 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
}
}
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
newCheck.AdditionalValidators = map[string]jsonschema.Schema{}
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
val := jsonschema.RootSchema{}
val := jsonschema.Schema{}
err := UnmarshalYAMLOrJSON([]byte(schemaStr), &val)
if err != nil {
return nil, err
@@ -268,48 +292,48 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
}
// CheckPodSpec checks a pod spec against the schema
func (check SchemaCheck) CheckPodSpec(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
return check.CheckObject(pod)
func (check SchemaCheck) CheckPodSpec(ctx context.Context, pod *corev1.PodSpec) (bool, []jsonschema.KeyError, error) {
return check.CheckObject(ctx, pod)
}
// CheckPodTemplate checks a pod template against the schema
func (check SchemaCheck) CheckPodTemplate(podTemplate interface{}) (bool, []jsonschema.ValError, error) {
return check.CheckObject(podTemplate)
func (check SchemaCheck) CheckPodTemplate(ctx context.Context, podTemplate any) (bool, []jsonschema.KeyError, error) {
return check.CheckObject(ctx, podTemplate)
}
// CheckController checks a controler's spec against the schema
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
errs, err := check.Validator.ValidateBytes(bytes)
func (check SchemaCheck) CheckController(ctx context.Context, bytes []byte) (bool, []jsonschema.KeyError, error) {
errs, err := check.Validator.ValidateBytes(ctx, bytes)
return len(errs) == 0, errs, err
}
// CheckContainer checks a container spec against the schema
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
return check.CheckObject(container)
func (check SchemaCheck) CheckContainer(ctx context.Context, container *corev1.Container) (bool, []jsonschema.KeyError, error) {
return check.CheckObject(ctx, container)
}
// CheckObject checks arbitrary data against the schema
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
func (check SchemaCheck) CheckObject(ctx context.Context, obj any) (bool, []jsonschema.KeyError, error) {
bytes, err := json.Marshal(obj)
if err != nil {
return false, nil, err
}
errs, err := check.Validator.ValidateBytes(bytes)
errs, err := check.Validator.ValidateBytes(ctx, bytes)
return len(errs) == 0, errs, err
}
// CheckAdditionalObjects looks for an object that passes the specified additional schema
func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []interface{}) (bool, error) {
func (check SchemaCheck) CheckAdditionalObjects(ctx context.Context, groupkind string, objects []any) (bool, error) {
val, ok := check.AdditionalValidators[groupkind]
if !ok {
return false, errors.New("No validator found for " + groupkind)
return false, errors.New("no validator found for " + groupkind)
}
for _, obj := range objects {
bytes, err := json.Marshal(obj)
if err != nil {
return false, err
}
errs, err := val.ValidateBytes(bytes)
errs, err := val.ValidateBytes(ctx, bytes)
if err != nil {
return false, err
}
@@ -335,19 +359,14 @@ func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit boo
return false
}
isIncluded := len(check.Controllers.Include) == 0
for _, inclusion := range check.Controllers.Include {
if inclusion == kind {
isIncluded = true
break
}
if slices.Contains(check.Controllers.Include, kind) {
isIncluded = true
}
if !isIncluded {
return false
}
for _, exclusion := range check.Controllers.Exclude {
if exclusion == kind {
return false
}
if slices.Contains(check.Controllers.Exclude, kind) {
return false
}
if check.Target == TargetContainer {
isIncluded := len(check.Containers.Include) == 0
+17 -13
View File
@@ -16,6 +16,7 @@ package dashboard
import (
"bytes"
"context"
"embed"
"encoding/json"
"html/template"
@@ -23,7 +24,6 @@ import (
"net/http"
"net/url"
"path"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
@@ -110,7 +110,9 @@ func writeTemplate(tmpl *template.Template, data *templateData, w http.ResponseW
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf.WriteTo(w)
if _, err := buf.WriteTo(w); err != nil {
logrus.Errorf("Error writing template: %v", err)
}
}
func getConfigForQuery(base config.Configuration, query url.Values) config.Configuration {
@@ -140,7 +142,7 @@ func stripUnselectedNamespaces(data *validator.AuditData, selectedNamespaces []s
}
// GetRouter returns a mux router serving all routes necessary for the dashboard
func GetRouter(c config.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) (*mux.Router, error) {
func GetRouter(ctx context.Context, c config.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) (*mux.Router, error) {
router := mux.NewRouter().PathPrefix(basePath).Subrouter()
assetsSubFS, err := fs.Sub(assetsFS, "assets")
@@ -152,7 +154,9 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
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"))
if _, err := w.Write([]byte("OK")); err != nil {
logrus.Errorf("Error writing health response: %v", err)
}
})
router.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
@@ -162,7 +166,9 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
http.Error(w, "Error getting favicon", http.StatusInternalServerError)
return
}
w.Write(favicon)
if _, err := w.Write(favicon); err != nil {
logrus.Errorf("Error writing favicon: %v", err)
}
})
router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) {
@@ -176,7 +182,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
}
var auditDataObj validator.AuditData
auditDataObj, err = validator.RunAudit(adjustedConf, k)
auditDataObj, err = validator.RunAudit(ctx, adjustedConf, k)
if err != nil {
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
return
@@ -187,11 +193,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
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)
})
router.HandleFunc("/details/{category}", func(http.ResponseWriter, *http.Request) {})
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path != basePath {
@@ -212,7 +214,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
logrus.Infof("Running audit")
var auditData validator.AuditData
auditData, err = validator.RunAudit(adjustedConf, k)
auditData, err = validator.RunAudit(ctx, adjustedConf, k)
if err != nil {
logrus.Errorf("Error getting audit data: %v", err)
http.Error(w, "Error running audit", 500)
@@ -264,5 +266,7 @@ func MainHandler(w http.ResponseWriter, r *http.Request, c config.Configuration,
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)
if err := json.NewEncoder(w).Encode(auditData); err != nil {
logrus.Errorf("Error encoding audit JSON: %v", err)
}
}
+8 -13
View File
@@ -15,7 +15,7 @@
package dashboard
import (
"fmt"
"slices"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
@@ -128,13 +128,13 @@ func getCategoryLink(category string) string {
func getCategoryInfo(category string) string {
switch category {
case "Reliability":
return fmt.Sprintf(`
return `
Kubernetes is built to reliabily run highly available applications.
Polaris includes a number of checks to ensure that you are maximizing
the reliability potential of Kubernetes.
`)
`
case "Efficiency":
return fmt.Sprintf(`
return `
Configuring resource requests and limits for workloads running in Kubernetes
helps ensure that every container will have access to all the resources it
needs. These are also a crucial part of cluster autoscaling logic, as new
@@ -142,25 +142,20 @@ func getCategoryInfo(category string) string {
infrastructure for new pod(s). By default, Polaris validates that resource
requests and limits are set, it also includes optional functionality to ensure
these requests and limits fall within specified ranges.
`)
`
case "Security":
return fmt.Sprintf(`
return `
Kubernetes provides a great deal of configurability when it comes to the
security of your workloads. A key principle here involves limiting the level
of access any individual workload has. Polaris has validations for a number of
best practices, mostly focused on ensuring that unnecessary access has not
been granted to an application workload.
`)
`
default:
return ""
}
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
return slices.Contains(list, a)
}
+3 -2
View File
@@ -1,6 +1,7 @@
package fix
import (
"context"
"errors"
"fmt"
"os"
@@ -19,7 +20,7 @@ const templateCloseMarker = "POLARIS_CLOSE_TMPL"
var ErrFilesPathRequired = errors.New("files-path flag is required")
func Execute(config config.Configuration, filesPath string, isTemplate bool, checksToFix ...string) error {
func Execute(ctx context.Context, config config.Configuration, filesPath string, isTemplate bool, checksToFix ...string) error {
if filesPath == "" {
return ErrFilesPathRequired
}
@@ -69,7 +70,7 @@ func Execute(config config.Configuration, filesPath string, isTemplate bool, che
if err != nil {
return fmt.Errorf("error creating resource provider from yaml: %v", err)
}
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
results, err := validator.ApplyAllSchemaChecksToResourceProvider(ctx, &config, kubeResources)
if err != nil {
return fmt.Errorf("error applying schema check to the resources %s: %v", fullFilePath, err)
}
-200
View File
@@ -1,200 +0,0 @@
package insights
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/sirupsen/logrus"
)
type cluster struct {
Name string `json:"Name"`
AuthToken string `json:"AuthToken"`
Organization string `json:"Organization"`
Status string `json:"Status"`
}
type reportJob struct {
ID int `json:"id"`
Status string `json:"status"`
TimeTakenInMs int `json:"timeTaken"`
}
type Client interface {
UpsertCluster(clusterName string) (*cluster, error)
SendReport(cluster cluster, reportType, reportVersion string, payload []byte) (*reportJob, error)
GetReportJob(clusterName string, reportJobID int) (*reportJob, error)
IsTokenValid() (bool, error)
}
type HTTPClient struct {
insightsHost string
organization string
token string
}
func NewHTTPClient(host, organization, token string) Client {
return HTTPClient{host, organization, token}
}
func (ic HTTPClient) UpsertCluster(clusterName string) (*cluster, error) {
clusterURL := fmt.Sprintf("%s/v0/organizations/%s/clusters/%s?showToken=true", ic.insightsHost, ic.organization, clusterName)
req, err := http.NewRequest("GET", clusterURL, nil)
if err != nil {
return nil, fmt.Errorf("building request for fetching cluster: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+ic.token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("making request fetching cluster: %w", err)
}
defer resp.Body.Close()
if isSuccessful2XX(resp.StatusCode) {
// cluster already created
logrus.Infof("cluster %q found...", clusterName)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
var c cluster
err = json.Unmarshal(body, &c)
if err != nil {
return nil, fmt.Errorf("unmarshaling response body: %w", err)
}
return &c, nil
} else {
logrus.Warnf("not able to fetch cluster, expected 2xx - received %d, will try to create", resp.StatusCode)
}
logrus.Infof("cluster %q not found... creating..", clusterName)
req, err = http.NewRequest("POST", clusterURL, nil)
if err != nil {
return nil, fmt.Errorf("building request for creating cluster: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+ic.token)
resp, err = http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("making request for creating cluster: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
if !isSuccessful2XX(resp.StatusCode) {
return nil, fmt.Errorf("creating cluster, expected 200 OK received %s: %v", resp.Status, string(body))
}
var c cluster
err = json.Unmarshal(body, &c)
if err != nil {
return nil, fmt.Errorf("unmarshaling response body: %w", err)
}
logrus.Infof("cluster %q created...", clusterName)
return &c, nil
}
func (ic HTTPClient) SendReport(cluster cluster, reportType, reportVersion string, payload []byte) (*reportJob, error) {
uploadReportURL := fmt.Sprintf("%s/v0/organizations/%s/clusters/%s/data/%s", ic.insightsHost, ic.organization, cluster.Name, reportType)
req, err := http.NewRequest("POST", uploadReportURL, bytes.NewBuffer(payload))
if err != nil {
return nil, fmt.Errorf("building request for output: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+cluster.AuthToken)
req.Header.Set("X-Fairwinds-Report-Version", reportVersion)
req.Header.Set("X-Fairwinds-Report-Priority", "4") // should have higher priority than the default 5
req.Header.Set("X-Fairwinds-Agent-Version", "")
req.Header.Set("X-Fairwinds-Agent-Chart-Version", "")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("making request for output: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
if !isSuccessful2XX(resp.StatusCode) {
return nil, fmt.Errorf("sending %s report, expected 200 OK received %s: %v", reportType, resp.Status, string(body))
}
var rj reportJob
err = json.Unmarshal(body, &rj)
if err != nil {
return nil, fmt.Errorf("unmarshaling response body: %w", err)
}
logrus.Debugf("%s report sent to fairwinds insights", reportType)
return &rj, nil
}
func isSuccessful2XX(statusCode int) bool {
return statusCode >= 200 && statusCode < 300
}
func (ic HTTPClient) GetReportJob(clusterName string, reportJobID int) (*reportJob, error) {
reportJobsURL := fmt.Sprintf("%s/v0/organizations/%s/clusters/%s/report-jobs/%d", ic.insightsHost, ic.organization, clusterName, reportJobID)
req, err := http.NewRequest("GET", reportJobsURL, nil)
if err != nil {
return nil, fmt.Errorf("building request for fetching report-job: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+ic.token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("making request fetching report-job: %w", err)
}
defer resp.Body.Close()
if !isSuccessful2XX(resp.StatusCode) {
return nil, fmt.Errorf("fetching report-job, expected 200 OK received %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
var rj reportJob
err = json.Unmarshal(body, &rj)
if err != nil {
return nil, fmt.Errorf("unmarshaling response body: %w", err)
}
return &rj, nil
}
// IsTokenValid checks if the token is valid by fetching the organization
func (ic HTTPClient) IsTokenValid() (bool, error) {
organizationURL := fmt.Sprintf("%s/v0/organizations/%s", ic.insightsHost, ic.organization)
req, err := http.NewRequest("GET", organizationURL, nil)
if err != nil {
return false, fmt.Errorf("building request for fetching organization: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+ic.token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, fmt.Errorf("making request fetching organization: %w", err)
}
defer resp.Body.Close()
if isSuccessful2XX(resp.StatusCode) {
return true, nil // token is valid
}
if resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
return false, nil // token is invalid
}
// unexpected error
return false, fmt.Errorf("fetching organization, expected 200 OK - received %s", resp.Status)
}
-89
View File
@@ -1,89 +0,0 @@
package insights
import (
"encoding/json"
"fmt"
"time"
workloads "github.com/fairwindsops/insights-plugins/plugins/workloads/pkg"
"github.com/fairwindsops/polaris/pkg/validator"
"github.com/sirupsen/logrus"
)
type insightsReporter struct {
client Client
}
func NewInsightsReporter(client Client) *insightsReporter {
return &insightsReporter{
client: client,
}
}
type WorkloadsReport struct {
Version string
Payload workloads.ClusterWorkloadReport
}
type PolarisReport struct {
Version string
Payload validator.AuditData
}
// ReportAuditToFairwindsInsights report audit to insights
// 1 - check if cluster exists, otherwise create it
// 2 - send workload report
// 3 - send polaris report
// 4 - checks if report job is completed for 3 minutes
// 5 - display link to Fairwinds Insights
func (ir insightsReporter) ReportAuditToFairwindsInsights(clusterName string, wr WorkloadsReport, pr PolarisReport) error {
cluster, err := ir.client.UpsertCluster(clusterName)
if err != nil {
return err
}
workloadsPayload, err := json.MarshalIndent(wr.Payload, "", " ")
if err != nil {
return fmt.Errorf("marshaling data: %w", err)
}
_, err = ir.client.SendReport(*cluster, "workloads", wr.Version, workloadsPayload)
if err != nil {
return err
}
polarisPayload, err := json.MarshalIndent(pr.Payload, "", " ")
if err != nil {
return fmt.Errorf("marshaling data: %w", err)
}
reportJob, err := ir.client.SendReport(*cluster, "polaris", pr.Version, polarisPayload)
if err != nil {
return err
}
success, err := verifyReportJobCompletion(&ir, cluster.Name, reportJob.ID)
if err != nil {
return err
}
if !success {
return fmt.Errorf("timed out waiting for report job to complete")
}
return nil
}
// verifyReportJobCompletion checks Insights for reportJob completion (timeout after 3 minutes)
func verifyReportJobCompletion(ir *insightsReporter, clusterName string, reportJobID int) (bool, error) {
defer func() { fmt.Println() }()
logrus.Println("Processing (this usually takes 1-3 minutes)...")
for i := 0; i < 60; i++ {
reportJob, err := ir.client.GetReportJob(clusterName, reportJobID)
if err != nil {
return false, err
}
if reportJob.Status == "completed" {
return true, nil
}
fmt.Print(".")
time.Sleep(3 * time.Second)
}
return false, nil
}
+13 -13
View File
@@ -39,13 +39,13 @@ type GenericResource struct {
ObjectMeta kubeAPIMetaV1.Object
Resource unstructured.Unstructured
PodSpec *kubeAPICoreV1.PodSpec
PodTemplate interface{}
PodTemplate any
OriginalObjectJSON []byte
OriginalObjectYAML []byte
}
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecMap interface{}) (GenericResource, error) {
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecMap any) (GenericResource, error) {
if unst.GetCreationTimestamp().Time.IsZero() {
unstructured.RemoveNestedField(unst.Object, "metadata", "creationTimestamp")
unstructured.RemoveNestedField(unst.Object, "status")
@@ -69,7 +69,7 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecM
return workload, err
}
workload.OriginalObjectJSON = b
m := make(map[string]interface{})
m := make(map[string]any)
err = json.Unmarshal(b, &m)
if err != nil {
return workload, err
@@ -93,7 +93,7 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecM
}
// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) {
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject any) (GenericResource, error) {
podMap, err := SerializePod(&podResource)
if err != nil {
return GenericResource{}, err
@@ -102,7 +102,7 @@ func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject int
Kind: "Pod",
PodSpec: &podResource.Spec,
PodTemplate: podMap,
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
ObjectMeta: podResource.GetObjectMeta(),
}
if originalObject != nil {
bytes, err := json.Marshal(originalObject)
@@ -157,9 +157,9 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
}
topKind := "Pod"
topMeta := podWorkload.ObjectMeta
var topPodSpec interface{}
var topPodSpec any
topPodSpec = podWorkload.Resource.Object
owners := podResource.ObjectMeta.GetOwnerReferences()
owners := podResource.GetOwnerReferences()
lastKey := ""
for len(owners) > 0 {
if len(owners) > 1 {
@@ -263,10 +263,10 @@ func GetObject(ctx context.Context, namespace, kind, version, name string, dynam
}
// GetPodSpec looks inside arbitrary YAML for a PodSpec
func GetPodSpec(yaml map[string]interface{}) interface{} {
func GetPodSpec(yaml map[string]any) any {
for _, child := range podSpecFields {
if childYaml, ok := yaml[child]; ok {
return GetPodSpec(childYaml.(map[string]interface{}))
return GetPodSpec(childYaml.(map[string]any))
}
}
if _, ok := yaml["containers"]; ok {
@@ -278,9 +278,9 @@ func GetPodSpec(yaml map[string]interface{}) interface{} {
// GetPodTemplate looks inside arbitrary YAML for a Pod template, containing
// fields `spec.containers`.
// For example, it returns the `spec.template` level of a Kubernetes Deployment yaml.
func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err error) {
func GetPodTemplate(yaml map[string]any) (podTemplate any, err error) {
if yamlSpec, ok := yaml["spec"]; ok {
if yamlSpecMap, ok := yamlSpec.(map[string]interface{}); ok {
if yamlSpecMap, ok := yamlSpec.(map[string]any); ok {
if _, ok := yamlSpecMap["containers"]; ok {
// This is a hack around unstructured.SetNestedField using DeepCopy which does
// not support the type int, and panics.
@@ -289,7 +289,7 @@ func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err e
if err != nil {
return nil, err
}
podTemplateMap := make(map[string]interface{})
podTemplateMap := make(map[string]any)
err = json.Unmarshal(podTemplateJSON, &podTemplateMap)
if err != nil {
return nil, err
@@ -300,7 +300,7 @@ func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err e
}
for _, podSpecField := range podSpecFields {
if childYaml, ok := yaml[podSpecField]; ok {
return GetPodTemplate(childYaml.(map[string]interface{}))
return GetPodTemplate(childYaml.(map[string]any))
}
}
return nil, nil
+48 -23
View File
@@ -52,6 +52,7 @@ type ResourceProvider struct {
SourceType string
Nodes []corev1.Node
Namespaces []corev1.Namespace
Pods []corev1.Pod
Resources resourceKindMap
}
@@ -109,13 +110,13 @@ func maybeTransformKindIntoGroupKind(k string) string {
}
func parseGroupKind(gk string) schema.GroupKind {
i := strings.Index(gk, "/")
if i == -1 {
before, after, ok := strings.Cut(gk, "/")
if !ok {
return schema.GroupKind{Kind: gk}
}
group := gk[:i]
kind := gk[i+1:]
group := before
kind := after
return schema.GroupKind{Group: group, Kind: kind}
}
@@ -127,6 +128,7 @@ func newResourceProvider(version, sourceType, sourceName string) ResourceProvide
CreationTime: time.Now(),
Nodes: make([]corev1.Node, 0),
Namespaces: make([]corev1.Namespace, 0),
Pods: make([]corev1.Pod, 0),
Resources: make(map[string][]GenericResource),
}
}
@@ -156,13 +158,13 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
}
serverVersion, err := clientSet.Discovery().ServerVersion()
if err != nil {
return nil, fmt.Errorf("Error fetching Cluster API version: %w", err)
return nil, fmt.Errorf("error fetching Cluster API version: %w", err)
}
resources := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Resource", workload)
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)
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]
@@ -171,11 +173,11 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
obj, err := GetObject(ctx, namespace, kind, version, name, dynamicClient, restMapper)
if err != nil {
return nil, fmt.Errorf("Could not find workload %s: %w", workload, err)
return nil, fmt.Errorf("could not find workload %s: %w", workload, err)
}
workloadObj, err := NewGenericResourceFromUnstructured(*obj, nil)
if err != nil {
return nil, fmt.Errorf("Could not parse workload %s: %w", workload, err)
return nil, fmt.Errorf("could not parse workload %s: %w", workload, err)
}
resources.Resources.addResource(workloadObj)
return &resources, nil
@@ -196,6 +198,9 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
}
visitFile := func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(path, ".yml") && !strings.HasSuffix(path, ".yaml") {
return nil
}
@@ -246,19 +251,19 @@ func GetKubeClient(ctx context.Context, kubeContext string) (dynamic.Interface,
kubeConf, err = config.GetConfig()
}
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error fetching KubeConfig: %v", err)
return nil, nil, nil, "", fmt.Errorf("error fetching KubeConfig: %v", err)
}
clientSet, err := kubernetes.NewForConfig(kubeConf)
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error creating Kubernetes client: %v", err)
return nil, nil, nil, "", fmt.Errorf("error creating Kubernetes client: %v", err)
}
dynamicClient, err := dynamic.NewForConfig(kubeConf)
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error connecting to dynamic interface: %v", err)
return nil, nil, nil, "", fmt.Errorf("error connecting to dynamic interface: %v", err)
}
resources, err := restmapper.GetAPIGroupResources(clientSet.Discovery())
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error getting API Group resources: %v", err)
return nil, nil, nil, "", fmt.Errorf("error getting API Group resources: %v", err)
}
return dynamicClient, restmapper.NewDiscoveryRESTMapper(resources), clientSet, kubeConf.Host, nil
}
@@ -304,6 +309,14 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
}
namespaces = nsList
}
logrus.Info("Loading pods")
pods, err := kube.CoreV1().Pods(c.Namespace).List(ctx, listOpts)
if err != nil {
logrus.Errorf("Error fetching Pods: %v", err)
return nil, err
}
logrus.Info("Setting up restmapper")
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
@@ -312,16 +325,22 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
}
restMapper := restmapper.NewDiscoveryRESTMapper(resources)
allChecks := []conf.SchemaCheck{}
for _, check := range c.CustomChecks {
allChecks = append(allChecks, check)
for checkID, check := range c.CustomChecks {
severity, enabled := c.Checks[checkID]
if enabled && severity.IsActionable() {
allChecks = append(allChecks, check)
}
}
for _, check := range conf.BuiltInChecks {
allChecks = append(allChecks, check)
for checkID, check := range conf.BuiltInChecks {
severity, enabled := c.Checks[checkID]
if enabled && severity.IsActionable() {
allChecks = append(allChecks, check)
}
}
var additionalKinds []conf.TargetKind
for _, check := range allChecks {
neededKinds := []conf.TargetKind{check.Target}
neededKinds := append([]conf.TargetKind{check.Target}, check.RelatedKinds...)
for key := range check.AdditionalSchemas {
neededKinds = append(neededKinds, conf.TargetKind(key))
}
@@ -340,6 +359,10 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
groupKind := parseGroupKind(maybeTransformKindIntoGroupKind(string(kind)))
mapping, err := restMapper.RESTMapping(groupKind)
if err != nil {
if meta.IsNoMatchError(err) {
logrus.Infof("Skipping unavailable Kind %s", kind)
continue
}
logrus.Warnf("error retrieving mapping of Kind %s because of error: %v", kind, err)
return nil, err
}
@@ -383,6 +406,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
provider.Nodes = nodes.Items
provider.Namespaces = namespaces.Items
provider.Pods = pods.Items
provider.Resources.addResources(kubernetesResources)
logrus.Info("Done loading Kubernetes resources")
return &provider, nil
@@ -442,6 +466,7 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
return err
}
workload.OriginalObjectYAML = contentBytes
resources.Pods = append(resources.Pods, pod)
resources.Resources.addResource(workload)
} else {
newResource, err := NewGenericResourceFromBytes(contentBytes)
@@ -454,12 +479,12 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
}
// SerializePodSpec converts a typed PodSpec into a map[string]interface{}
func SerializePodSpec(pod *corev1.PodSpec) (map[string]interface{}, error) {
func SerializePodSpec(pod *corev1.PodSpec) (map[string]any, error) {
podJSON, err := json.Marshal(pod)
if err != nil {
return nil, err
}
podMap := make(map[string]interface{})
podMap := make(map[string]any)
err = json.Unmarshal(podJSON, &podMap)
if err != nil {
return nil, err
@@ -468,12 +493,12 @@ func SerializePodSpec(pod *corev1.PodSpec) (map[string]interface{}, error) {
}
// SerializePod converts a typed Pod into a map[string]interface{}
func SerializePod(pod *corev1.Pod) (map[string]interface{}, error) {
func SerializePod(pod *corev1.Pod) (map[string]any, error) {
podJSON, err := json.Marshal(pod)
if err != nil {
return nil, err
}
podMap := make(map[string]interface{})
podMap := make(map[string]any)
err = json.Unmarshal(podJSON, &podMap)
if err != nil {
return nil, err
@@ -482,12 +507,12 @@ func SerializePod(pod *corev1.Pod) (map[string]interface{}, error) {
}
// SerializeContainer converts a typed Container into a map[string]interface{}
func SerializeContainer(container *corev1.Container) (map[string]interface{}, error) {
func SerializeContainer(container *corev1.Container) (map[string]any, error) {
containerJSON, err := json.Marshal(container)
if err != nil {
return nil, err
}
containerMap := make(map[string]interface{})
containerMap := make(map[string]any)
err = json.Unmarshal(containerJSON, &containerMap)
if err != nil {
return nil, err
+48 -5
View File
@@ -40,7 +40,7 @@ func TestGetResourcesFromPath(t *testing.T) {
assert.Equal(t, 0, len(provider.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(provider.Namespaces), "Should have a namespace")
assert.Equal(t, "two", provider.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, "two", provider.Namespaces[0].Name)
namespaceCount := map[string]int{}
for _, resources := range provider.Resources {
@@ -69,8 +69,8 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
assert.Equal(t, "dashboard", resources.Resources["apps/Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].ObjectMeta.Name)
assert.Equal(t, "polaris", resources.Namespaces[0].Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].Name)
}
func TestGetMultipleResourceFromBadFile(t *testing.T) {
@@ -92,8 +92,8 @@ func TestAddResourcesFromReader(t *testing.T) {
assert.Equal(t, "dashboard", resources.Resources["apps/Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].ObjectMeta.Name)
assert.Equal(t, "polaris", resources.Namespaces[0].Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].Name)
}
func TestGetResourceFromAPI(t *testing.T) {
@@ -157,6 +157,7 @@ func TestGetResourceFromAPI(t *testing.T) {
assert.Equal(t, tt.want.SourceName, resources.SourceName)
assert.IsType(t, tt.want.CreationTime, resources.CreationTime)
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 5, len(resources.Pods), "Should have 5 pods")
for k, v := range resources.Resources {
fmt.Println("cont", k, v)
}
@@ -175,3 +176,45 @@ func TestGetResourceFromAPI(t *testing.T) {
})
}
}
func TestAdditionalKindLoading(t *testing.T) {
ingress := test.MockIngress()
k8s, dynamicInterface := test.SetupTestAPI(append(test.GetMockControllers("test"), &ingress)...)
enabled := conf.Configuration{
Checks: map[string]conf.Severity{
"customIngress": conf.SeverityWarning,
},
CustomChecks: map[string]conf.SchemaCheck{
"customIngress": {Target: "networking.k8s.io/Ingress"},
},
}
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicInterface, enabled)
if assert.NoError(t, err) {
assert.Len(t, resources.Resources["networking.k8s.io/Ingress"], 1)
}
ignored := enabled
ignored.Checks = map[string]conf.Severity{
"customIngress": conf.SeverityIgnore,
}
resources, err = CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicInterface, ignored)
if assert.NoError(t, err) {
assert.Empty(t, resources.Resources["networking.k8s.io/Ingress"])
}
}
func TestUnavailableAdditionalKindDoesNotFailAudit(t *testing.T) {
k8s, dynamicInterface := test.SetupTestAPI(test.GetMockControllers("test")...)
config := conf.Configuration{
Checks: map[string]conf.Severity{
"optionalCRD": conf.SeverityWarning,
},
CustomChecks: map[string]conf.SchemaCheck{
"optionalCRD": {Target: "example.com/OptionalResource"},
},
}
_, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicInterface, config)
assert.NoError(t, err)
}
+5 -5
View File
@@ -161,7 +161,6 @@ func createPathAndFindNodes(node *yaml.Node, selectors []string, create bool) ([
node.Value = ""
case yaml.SequenceNode:
return nil, errors.Errorf("parent node is array, use /*/ or /0/../%v/ instead of .%v to access its item(s) first", len(node.Content)-1, currentSelector)
default:
return nil, errors.Errorf("parent node is of unknown kind %v", node.Kind)
}
@@ -174,7 +173,7 @@ func createPathAndFindNodes(node *yaml.Node, selectors []string, create bool) ([
func addOrReplaceValue(node *yaml.Node, splits []string, value *yaml.Node) error {
if len(node.Content) == 0 {
return errors.New("No content in node")
return errors.New("no content in node")
}
nodes, err := createPathAndFindNodes(node.Content[0], splits, true)
if err != nil {
@@ -225,7 +224,7 @@ func addOrReplaceValue(node *yaml.Node, splits []string, value *yaml.Node) error
return nil
}
func getNodeFromValue(value interface{}, comment string) (*yaml.Node, error) {
func getNodeFromValue(value any, comment string) (*yaml.Node, error) {
bytes, err := yaml.Marshal(value)
if err != nil {
return nil, err
@@ -236,7 +235,7 @@ func getNodeFromValue(value interface{}, comment string) (*yaml.Node, error) {
return nil, err
}
if len(doc.Content) == 0 {
return nil, errors.New("Generated an empty YAML document")
return nil, errors.New("generated an empty YAML document")
}
if doc.Content[0].Kind == yaml.MappingNode {
doc.Content[0].Content[0].HeadComment = comment
@@ -294,7 +293,8 @@ func removeMatchingNode(node *yaml.Node, selectors []string) error {
for _, node := range visitArrayNodes {
lastSelector := len(selectors) == 1
if !lastSelector {
removeMatchingNode(node, selectors[1:])
// Missing nested keys are expected for * wildcards (e.g. owners/*/aliases).
_ = removeMatchingNode(node, selectors[1:])
}
}
}

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