Compare commits

..
21 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
74 changed files with 1722 additions and 488 deletions
+108 -207
View File
@@ -1,110 +1,21 @@
## DO NOT EDIT - Managed by Terraform
version: 2.1
orbs:
rok8s: fairwinds/rok8s-scripts@14
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
# 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
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
@@ -113,146 +24,144 @@ references:
sudo apt-get install -y qemu-user-static binfmt-support
docker buildx create --use || true
docker buildx inspect --bootstrap
load_polaris_image_into_kind: &load_polaris_image_into_kind
run:
name: Load Polaris snapshot image into Kind
command: |
TAR="/tmp/workspace/docker_save/polaris_${CIRCLE_SHA1}.tar"
if [ -f "$TAR" ]; then
echo "Loading snapshot image from $TAR"
docker load -i "$TAR"
kind load docker-image "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}"
else
echo "No snapshot tar (tag workflow); tests will pull the image from the registry."
fi
jobs:
test_k8s:
working_directory: ~/polaris
resource_class: medium
executor: vm
steps:
- checkout
- *set_environment_variables
- attach_workspace:
at: /tmp/workspace
- *install_k8s
- *load_polaris_image_into_kind
- *test_k8s
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.26.4
- 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
release:
working_directory: /go/src/github.com/fairwindsops/polaris/
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.16.0
steps:
- checkout
- setup_remote_docker
- *install_vault_alpine
- rok8s/get_vault_env:
vault_path: repo/global/env
- rok8s/get_vault_env:
vault_path: repo/polaris/env
- *set_environment_variables
- run:
name: docker login Google Artifact Registry
name: Go Mod Download
command: go mod download && go mod verify
- run:
name: golangci-lint
command: |
docker login -u _json_key -p "$(echo $GCP_ARTIFACTREADWRITE_JSON_KEY | base64 -d)" us-docker.pkg.dev
- install_goreleaser_dependencies
- run: echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
- run: goreleaser release --clean
- run: mkdir -p /tmp/workspace/docker_save && touch /tmp/workspace/docker_save/.release-workspace-marker
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
root: /tmp/workspace/
paths:
- docker_save
snapshot:
working_directory: /home/circleci/project
- store_artifacts:
path: dist
destination: snapshot
release:
machine:
image: ubuntu-2204:current
resource_class: large
shell: /bin/bash
steps:
- checkout
- *install_vault_machine
- rok8s/get_vault_env:
vault_path: repo/global/env
- rok8s/get_vault_env:
vault_path: repo/polaris/env
- 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: goreleaser release (snapshot, local only)
environment:
GORELEASER_SKIP_RELEASE: "true"
name: Run GoReleaser release
command: |
set -euo pipefail
REPO_ROOT="${HOME}/project"
export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "${REPO_ROOT}:/workspace" -w /workspace \
-e GORELEASER_SKIP_RELEASE=true \
-e CIRCLE_SHA1 \
-e CIRCLE_BRANCH \
-v "$(pwd):/workspace" -w /workspace \
-v "${HOME}/.docker:/root/.docker" \
-e GORELEASER_CURRENT_TAG \
-e CIRCLE_TAG \
--entrypoint sh \
goreleaser/goreleaser:v2.16.0 \
-c 'git config --global --add safe.directory /workspace && cd /workspace && goreleaser release --snapshot --clean --skip=sign,homebrew'
- run: mkdir -p /tmp/workspace/docker_save/
- run:
name: Tag and save amd64 image for Kind
command: |
set -euo pipefail
docker tag "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}-amd64" "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}"
docker save "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}" -o "/tmp/workspace/docker_save/polaris_${CIRCLE_SHA1}.tar"
- persist_to_workspace:
root: /tmp/workspace
paths:
- docker_save
- store_artifacts:
path: dist
destination: snapshot
-e CIRCLE_SHA1 \
-e GO111MODULE=on \
-e GITHUB_TOKEN \
-e VAULT_ADDR \
-e VAULT_TOKEN \
goreleaser/goreleaser:v2.17.1 release
workflows:
version: 2
branch:
test_and_build:
jobs:
- test:
filters:
tags:
ignore: /.*/
- snapshot:
filters:
branches:
ignore: /pull\/[0-9]+/
tags:
ignore: /.*/
- test_k8s:
requires:
- snapshot
- test
filters:
branches:
ignore: /pull\/[0-9]+/
only: /.*/
tags:
ignore: /.*/
tag_release:
- rok8s/kubernetes_e2e_tests:
name: "kubernetes e2e"
kind_node_image: "kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a"
<<: *e2e_configuration
release:
jobs:
- test:
filters:
tags:
only: /.*/
branches:
ignore: /.*/
tags:
only: /v.*/
- release:
requires:
- test
@@ -261,21 +170,13 @@ workflows:
branches:
ignore: /.*/
tags:
ignore: /^testing-.*/
- test_k8s:
requires:
- release
filters:
branches:
ignore: /.*/
tags:
ignore: /^testing-.*/
only: /v.*/
- oss-docs/publish-docs:
requires:
- test_k8s
- 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.24.1
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"
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10.3.0
- uses: actions/stale@v4
with:
exempt-issue-labels: pinned
stale-pr-label: stale
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
build-int:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- 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@v7.0.0
- uses: actions/checkout@v6.0.2
- name: Setup polaris
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
+18 -9
View File
@@ -189,17 +189,18 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
var outputBytes []byte
var err error
if outputFormat == "score" {
switch outputFormat {
case "score":
outputBytes = fmt.Appendf(nil, "%d\n", auditData.GetSummary().GetScore())
} else if outputFormat == "yaml" {
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 {
@@ -207,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))
@@ -217,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")
}
@@ -236,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)
+3 -1
View File
@@ -61,7 +61,9 @@ var dashboardCmd = &cobra.Command{
}
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)
+3 -1
View File
@@ -48,7 +48,9 @@ var fixCommand = &cobra.Command{
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)
+3 -2
View File
@@ -29,7 +29,6 @@ var (
disallowExemptions bool
disallowConfigExemptions bool
disallowAnnotationExemptions bool
fixChecks bool
logLevel string
auditPath string
displayName string
@@ -88,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)
}
}
},
}
+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.
+4 -4
View File
@@ -9,7 +9,7 @@
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"vuepress-plugin-check-md": "0.0.3"
"vuepress-plugin-check-md": "0.0.2"
},
"devDependencies": {
"vuepress": "^1.9.7",
@@ -16972,9 +16972,9 @@
}
},
"node_modules/vuepress-plugin-check-md": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/vuepress-plugin-check-md/-/vuepress-plugin-check-md-0.0.3.tgz",
"integrity": "sha512-ov9lw0g/b4ZIxjr3dcwYNDwMpfxLaOBhgOsDLnehdphx1eqaeJQ8MZO8PODE6aQWUuIUBzHOM3xQ9Kuvps3X3g==",
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/vuepress-plugin-check-md/-/vuepress-plugin-check-md-0.0.2.tgz",
"integrity": "sha512-XwA/IiMNvR42L3ajmkr+6JY3JRnhDN+uluh1wLYl0VAI8VqTkXT7Ng4xlxgebfLPChEFPnJgcydGv8E52Zdpig==",
"license": "MIT",
"dependencies": {
"check-md": "1.0.0"
+1 -1
View File
@@ -4,7 +4,7 @@
"url": "https://github.com/FairwindsOps/insights-docs/issues"
},
"dependencies": {
"vuepress-plugin-check-md": "0.0.3"
"vuepress-plugin-check-md": "0.0.2"
},
"description": "A repository with a Vuepress template for Fairwinds projects",
"devDependencies": {
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
+34 -42
View File
@@ -1,6 +1,6 @@
module github.com/fairwindsops/polaris
go 1.26.4
go 1.26.2
require (
github.com/fairwindsops/controller-utils v0.3.4
@@ -9,86 +9,78 @@ require (
github.com/pkg/errors v0.9.1
github.com/qri-io/jsonpointer v0.1.1
github.com/qri-io/jsonschema v0.2.1
github.com/sirupsen/logrus v1.9.4
github.com/sirupsen/logrus v1.10.1
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.12.1
github.com/thoas/go-funk v0.9.3
gomodules.xyz/jsonpatch/v2 v2.5.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.36.2
k8s.io/apimachinery v0.36.2
k8s.io/client-go v0.36.2
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/Masterminds/semver/v3 v3.5.0 // indirect
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.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // 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.10.1 // indirect
github.com/fxamacker/cbor/v2 v2.9.2 // 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.24.0 // indirect
github.com/go-openapi/jsonreference v0.21.6 // indirect
github.com/go-openapi/swag v0.27.0 // indirect
github.com/go-openapi/swag/cmdutils v0.27.0 // indirect
github.com/go-openapi/swag/conv v0.27.0 // indirect
github.com/go-openapi/swag/fileutils v0.27.0 // indirect
github.com/go-openapi/swag/jsonutils v0.27.0 // indirect
github.com/go-openapi/swag/loading v0.27.0 // indirect
github.com/go-openapi/swag/mangling v0.27.0 // indirect
github.com/go-openapi/swag/netutils v0.27.0 // indirect
github.com/go-openapi/swag/stringutils v0.27.0 // indirect
github.com/go-openapi/swag/typeutils v0.27.0 // indirect
github.com/go-openapi/swag/yamlutils v0.27.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/pprof v0.0.0-20260604005048-7023385849c0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.19.0 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.22 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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/onsi/ginkgo/v2 v2.32.0 // indirect
github.com/onsi/gomega v1.42.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.69.0 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/rogpeppe/go-internal v1.15.0 // 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/sergi/go-diff v1.4.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/zap v1.28.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.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.38.0 // indirect
golang.org/x/text v0.39.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.47.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
k8s.io/apiextensions-apiserver v0.36.2 // indirect
k8s.io/apiextensions-apiserver v0.36.0 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260624041617-8f3fa4921821 // indirect
k8s.io/utils v0.0.0-20260626114624-be93311217bd // 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.4.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect
)
+87 -88
View File
@@ -1,28 +1,29 @@
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
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.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.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.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
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.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/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.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -30,38 +31,40 @@ 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.24.0 h1:AA6mCjHYHmZ+1RU2Js089EaOK/iwXXNwQsTgnsTha2M=
github.com/go-openapi/jsonpointer v0.24.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y=
github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY=
github.com/go-openapi/swag v0.27.0 h1:8ecSuZlh4NXc3GsmAOqECIYqDTApCWaMe3gO4gjJNEE=
github.com/go-openapi/swag v0.27.0/go.mod h1:Kkgz9Ht0+ul9/aVdFmc9xSyPzUwf/aFF5KiFPBXfSY0=
github.com/go-openapi/swag/cmdutils v0.27.0 h1:aIKiqhB29AaP+7xm8/CPg3uOpeHx2SUp6TvMpu/a31Y=
github.com/go-openapi/swag/cmdutils v0.27.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
github.com/go-openapi/swag/conv v0.27.0 h1:EKOH4feXrvdo8DbSsXSAqRT8fz1epEnS5O2IfXUOzE8=
github.com/go-openapi/swag/conv v0.27.0/go.mod h1:pfiv0uKQTbaGApk8Zs/lZV3uSjmSpa2FO1y183YngN8=
github.com/go-openapi/swag/fileutils v0.27.0 h1:ib5jMUqGq5tY1EyO4inlrabsaeDAleFU+XD1FXQcgp8=
github.com/go-openapi/swag/fileutils v0.27.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
github.com/go-openapi/swag/jsonutils v0.27.0 h1:VYtd9jEQYeU4j8q5vdn5KWotF4vKywhGdMBrALtAsfE=
github.com/go-openapi/swag/jsonutils v0.27.0/go.mod h1:U7pb8AGuwhok3RDicHeHwSG4L3PXSq6PAL98Aon632g=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.0 h1:+d7C7Ur/SsGg/UZ9G0JEovnfRqtMNZCJQGKc2h/ojoE=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
github.com/go-openapi/swag/loading v0.27.0 h1:s8DA9aPEdFH6OluHUYUn3DnIuoTdyWs9RwffXBUfyeI=
github.com/go-openapi/swag/loading v0.27.0/go.mod h1:VOz+Jg6UGGywcmRvYsI4fvtp+bd7NfioseGEPleYdA4=
github.com/go-openapi/swag/mangling v0.27.0 h1:rpPJuqQHa6z2pDiP3iIpXOyNXlSs9cQCxnJSAxzdfOc=
github.com/go-openapi/swag/mangling v0.27.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
github.com/go-openapi/swag/netutils v0.27.0 h1:lEUG+hHvPvLggB3A8snFk0IRKNf9uC0YKc+7WYqvAF8=
github.com/go-openapi/swag/netutils v0.27.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
github.com/go-openapi/swag/stringutils v0.27.0 h1:Of7w/HljWsNZvuxsUAnw3n+hCOyI6HLJOxW2kQRAxio=
github.com/go-openapi/swag/stringutils v0.27.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
github.com/go-openapi/swag/typeutils v0.27.0 h1:aCf4MSGo8NLwZP8Q6t32DWLJSvl/WwNqgmEG+xJ6v2o=
github.com/go-openapi/swag/typeutils v0.27.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.27.0 h1:bQ6eAMil5X9tdcf7dMn4t15alzG6jddnrKPuKa/zxKM=
github.com/go-openapi/swag/yamlutils v0.27.0/go.mod h1:yRfIo7qqVkmJRQjX8exjA3AfcI8rH1KDNPsTparoCv4=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
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/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
@@ -71,8 +74,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
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-20260604005048-7023385849c0 h1:h1QTMDl6q9wDvDCJVpKQSjgleGFYnd2fOxmg2K+6BGE=
github.com/google/pprof v0.0.0-20260604005048-7023385849c0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
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=
@@ -81,21 +84,18 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
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/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=
@@ -104,10 +104,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
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.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E=
github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
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=
@@ -117,24 +117,23 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
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.69.0 h1:OA85nJQS/T/MaYh/Q2CcgDKSGWqNIgrBDvDH85CuiNk=
github.com/prometheus/common v0.69.0/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y=
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
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.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.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
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.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/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
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=
@@ -145,8 +144,8 @@ 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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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=
@@ -155,12 +154,13 @@ 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.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
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=
@@ -169,12 +169,13 @@ 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.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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
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=
@@ -184,7 +185,6 @@ gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuB
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-20190902080502-41f04d3bba15/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.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
@@ -192,30 +192,29 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM
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.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY=
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg=
k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4=
k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA=
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI=
k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0=
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-20260624041617-8f3fa4921821 h1:m2wZhD5+vJZyCVkTvUHIfaiXc/mdt3Pxyx3vUnGsKzU=
k8s.io/kube-openapi v0.0.0-20260624041617-8f3fa4921821/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY=
k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs=
k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
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.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo=
sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
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=
+9
View File
@@ -57,6 +57,15 @@ var (
"sensitiveContainerEnvVar",
// Other checks
"tlsSettingsMissing",
"gatewayTLSMissing",
"gatewayAllowedRoutesAll",
"gatewayInsecureFrontendValidation",
"gatewayCrossNamespaceCertificateRef",
"httpRouteWildcardOrEmptyHost",
"httpRouteInsecureListener",
"httpRouteCrossNamespaceBackendRef",
"httpRouteBackendTLSMissing",
"kgatewayBackendTLSVerificationDisabled",
"pdbDisruptionsIsZero",
"metadataAndInstanceMismatched",
"missingPodDisruptionBudget",
@@ -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
@@ -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: '^[^*]+$'
@@ -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
+2 -2
View File
@@ -108,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 {
@@ -127,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
}
+5 -2
View File
@@ -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() {
@@ -155,6 +157,7 @@ func TestConfigWithCustomChecks(t *testing.T) {
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]any{})
assert.NoError(t, err)
isValid, _, err := check.CheckObject(context.TODO(), valid)
assert.NoError(t, err)
assert.Equal(t, true, isValid)
+9
View File
@@ -38,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
+9
View File
@@ -38,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
+6 -3
View File
@@ -80,6 +80,7 @@ type SchemaCheck struct {
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"`
}
@@ -95,7 +96,7 @@ func UnmarshalYAMLOrJSON(raw []byte, dest any) 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
@@ -108,7 +109,9 @@ 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
}
@@ -323,7 +326,7 @@ func (check SchemaCheck) CheckObject(ctx context.Context, obj any) (bool, []json
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)
+13 -10
View File
@@ -24,7 +24,6 @@ import (
"net/http"
"net/url"
"path"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
@@ -111,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 {
@@ -153,7 +154,9 @@ func GetRouter(ctx context.Context, c config.Configuration, auditPath string, po
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) {
@@ -163,7 +166,9 @@ func GetRouter(ctx context.Context, c config.Configuration, auditPath string, po
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) {
@@ -188,11 +193,7 @@ func GetRouter(ctx context.Context, c config.Configuration, auditPath string, po
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 {
@@ -265,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)
}
}
+6 -7
View File
@@ -15,7 +15,6 @@
package dashboard
import (
"fmt"
"slices"
"strings"
@@ -129,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
@@ -143,15 +142,15 @@ 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 ""
}
+2 -2
View File
@@ -102,7 +102,7 @@ func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject any
Kind: "Pod",
PodSpec: &podResource.Spec,
PodTemplate: podMap,
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
ObjectMeta: podResource.GetObjectMeta(),
}
if originalObject != nil {
bytes, err := json.Marshal(originalObject)
@@ -159,7 +159,7 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
topMeta := podWorkload.ObjectMeta
var topPodSpec any
topPodSpec = podWorkload.Resource.Object
owners := podResource.ObjectMeta.GetOwnerReferences()
owners := podResource.GetOwnerReferences()
lastKey := ""
for len(owners) > 0 {
if len(owners) > 1 {
+38 -13
View File
@@ -52,6 +52,7 @@ type ResourceProvider struct {
SourceType string
Nodes []corev1.Node
Namespaces []corev1.Namespace
Pods []corev1.Pod
Resources resourceKindMap
}
@@ -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)
+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)
}
+4 -3
View File
@@ -173,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 {
@@ -235,7 +235,7 @@ func getNodeFromValue(value any, 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
@@ -293,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:])
}
}
}
+1
View File
@@ -35,6 +35,7 @@ func TestValidatePDB(t *testing.T) {
}
pdb := unstructured.Unstructured{}
res, err := kube.NewGenericResourceFromUnstructured(pdb, nil)
assert.NoError(t, err)
res.Kind = "PodDisruptionBudget"
actualResult, err := applyNonControllerSchemaChecks(context.Background(), &c, nil, res)
+2 -1
View File
@@ -103,6 +103,7 @@ func TestControllerLevelChecks(t *testing.T) {
b, err := json.Marshal(replicaSpec)
assert.NoError(t, err)
err = json.Unmarshal(b, &replicaSpec)
assert.NoError(t, err)
d1, p1 := test.MockDeploy("test", "test-deployment")
d2, p2 := test.MockDeploy("test", "test-deployment-2")
@@ -203,7 +204,7 @@ func TestControllerExemptions(t *testing.T) {
}
pod := test.MockPod()
pod.ObjectMeta.Namespace = "foo"
pod.Namespace = "foo"
workload, err := kube.NewGenericResourceFromPod(pod, nil)
assert.NoError(t, err)
workload.Kind = "Deployment"
+3 -3
View File
@@ -50,6 +50,7 @@ func RunAudit(ctx context.Context, config conf.Configuration, kubeResources *kub
ClusterInfo: ClusterInfo{
Version: kubeResources.ServerVersion,
Nodes: len(kubeResources.Nodes),
Pods: len(kubeResources.Pods),
Namespaces: len(kubeResources.Namespaces),
Controllers: kubeResources.Resources.GetNumberOfControllers(),
},
@@ -61,13 +62,12 @@ func RunAudit(ctx context.Context, config conf.Configuration, kubeResources *kub
// ReadAuditFromFile reads the data from a past audit stored in a JSON or YAML file.
func ReadAuditFromFile(fileName string) AuditData {
auditData := AuditData{}
oldFileBytes, err := os.ReadFile(fileName)
if err != nil {
logrus.Errorf("Unable to read contents of loaded file: %v", err)
os.Exit(1)
}
auditData, err = ParseAudit(oldFileBytes)
auditData, err := ParseAudit(oldFileBytes)
if err != nil {
logrus.Errorf("Error parsing file contents into auditData: %v", err)
os.Exit(1)
@@ -85,7 +85,7 @@ func ParseAudit(oldFileBytes []byte) (AuditData, error) {
if err == io.EOF {
return conf, nil
}
return conf, fmt.Errorf("Decoding config failed: %v", err)
return conf, fmt.Errorf("decoding config failed: %v", err)
}
}
}
+3
View File
@@ -51,6 +51,9 @@ func TestGetTemplateData(t *testing.T) {
assert.EqualValues(t, sum, actualAudit.GetSummary())
assert.Equal(t, actualAudit.SourceType, "Cluster", "should be from a cluster")
assert.Equal(t, actualAudit.SourceName, "test", "should be from a cluster")
assert.Equal(t, 5, actualAudit.ClusterInfo.Pods, "should report pod count from the cluster")
assert.Equal(t, 5, actualAudit.ClusterInfo.Controllers, "should report controller count from the cluster")
assert.Equal(t, 1, actualAudit.ClusterInfo.Namespaces, "should report namespace count from the cluster")
expectedResults := []struct {
kind string
+485
View File
@@ -0,0 +1,485 @@
package validator
import (
"encoding/json"
"fmt"
"strings"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/qri-io/jsonschema"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
const (
gatewayAPIGroup = "gateway.networking.k8s.io"
kgatewayAPIGroup = "gateway.kgateway.dev"
)
type objectReference struct {
group string
kind string
name string
namespace string
sectionName string
port int64
}
func init() {
registerCustomChecks("httpRouteInsecureListener", httpRouteInsecureListener)
registerCustomChecks("gatewayCrossNamespaceCertificateRef", gatewayCrossNamespaceCertificateRef)
registerCustomChecks("httpRouteCrossNamespaceBackendRef", httpRouteCrossNamespaceBackendRef)
registerCustomChecks("httpRouteBackendTLSMissing", httpRouteBackendTLSMissing)
}
func httpRouteInsecureListener(test schemaTestCase) (bool, []jsonschema.KeyError, error) {
if isFullHTTPSRedirect(test.Resource.Resource.Object) || test.ResourceProvider == nil {
return true, nil, nil
}
routeNamespace := test.Resource.ObjectMeta.GetNamespace()
for _, parent := range referencesAt(test.Resource.Resource.Object, "spec", "parentRefs") {
parent = withDefaults(parent, gatewayAPIGroup, "Gateway", routeNamespace)
if parent.group != gatewayAPIGroup || parent.kind != "Gateway" {
continue
}
gateway := findResource(test.ResourceProvider.Resources[gatewayAPIGroup+"/Gateway"], parent.namespace, parent.name)
if gateway == nil {
continue
}
listeners := nestedSlice(gateway.Resource.Object, "spec", "listeners")
for _, rawListener := range listeners {
listener, ok := rawListener.(map[string]any)
if !ok || (parent.sectionName != "" && stringValue(listener["name"]) != parent.sectionName) {
continue
}
if stringValue(listener["protocol"]) == "HTTP" && listenerAcceptsHTTPRoute(listener, parent.namespace, test.Resource, test.ResourceProvider) {
return gatewayFailure("spec.parentRefs", fmt.Sprintf("HTTPRoute references HTTP listener %q on Gateway %s/%s without a full HTTPS redirect", stringValue(listener["name"]), parent.namespace, parent.name))
}
}
}
return true, nil, nil
}
func gatewayCrossNamespaceCertificateRef(test schemaTestCase) (bool, []jsonschema.KeyError, error) {
if test.ResourceProvider == nil {
return true, nil, nil
}
sourceNamespace := test.Resource.ObjectMeta.GetNamespace()
listeners := nestedSlice(test.Resource.Resource.Object, "spec", "listeners")
for _, rawListener := range listeners {
listener, ok := rawListener.(map[string]any)
if !ok {
continue
}
for _, ref := range referencesAt(listener, "tls", "certificateRefs") {
ref = withDefaults(ref, "", "Secret", sourceNamespace)
if ref.namespace != sourceNamespace && !hasReferenceGrant(test.ResourceProvider, sourceNamespace, "Gateway", ref) {
return gatewayFailure("spec.listeners.tls.certificateRefs", fmt.Sprintf("Gateway %s/%s references %s %s/%s without a matching ReferenceGrant", sourceNamespace, test.Resource.ObjectMeta.GetName(), ref.kind, ref.namespace, ref.name))
}
}
}
return true, nil, nil
}
func httpRouteCrossNamespaceBackendRef(test schemaTestCase) (bool, []jsonschema.KeyError, error) {
if test.ResourceProvider == nil {
return true, nil, nil
}
sourceNamespace := test.Resource.ObjectMeta.GetNamespace()
for _, ref := range httpRouteBackendRefs(test.Resource.Resource.Object) {
ref = withDefaults(ref, "", "Service", sourceNamespace)
if ref.namespace != sourceNamespace && !hasReferenceGrant(test.ResourceProvider, sourceNamespace, "HTTPRoute", ref) {
return gatewayFailure("spec.rules.backendRefs", fmt.Sprintf("HTTPRoute %s/%s references %s %s/%s without a matching ReferenceGrant", sourceNamespace, test.Resource.ObjectMeta.GetName(), ref.kind, ref.namespace, ref.name))
}
}
return true, nil, nil
}
func httpRouteBackendTLSMissing(test schemaTestCase) (bool, []jsonschema.KeyError, error) {
if test.ResourceProvider == nil {
return true, nil, nil
}
routeNamespace := test.Resource.ObjectMeta.GetNamespace()
for _, ref := range httpRouteBackendRefs(test.Resource.Resource.Object) {
ref = withDefaults(ref, "", "Service", routeNamespace)
if !backendUsesTLS(test.ResourceProvider, ref) {
continue
}
if hasBackendTLSPolicy(test.ResourceProvider, ref) || hasKgatewayBackendTLSPolicy(test.ResourceProvider, ref) {
continue
}
return gatewayFailure("spec.rules.backendRefs", fmt.Sprintf("HTTPRoute backend %s %s/%s appears to use TLS but has no BackendTLSPolicy or kgateway BackendConfigPolicy", ref.kind, ref.namespace, ref.name))
}
return true, nil, nil
}
func isFullHTTPSRedirect(object map[string]any) bool {
rules := nestedSlice(object, "spec", "rules")
if len(rules) == 0 {
return false
}
for _, rawRule := range rules {
rule, ok := rawRule.(map[string]any)
if !ok || len(referencesAt(rule, "backendRefs")) > 0 || !ruleMatchesAllTraffic(rule) || !hasHTTPSRedirect(rule) {
return false
}
}
return true
}
func ruleMatchesAllTraffic(rule map[string]any) bool {
matches := nestedSlice(rule, "matches")
if len(matches) == 0 {
return true
}
for _, rawMatch := range matches {
match, ok := rawMatch.(map[string]any)
if !ok || len(match) != 1 {
continue
}
path, ok := match["path"].(map[string]any)
if ok && (stringValue(path["type"]) == "" || stringValue(path["type"]) == "PathPrefix") && stringValue(path["value"]) == "/" {
return true
}
}
return false
}
func hasHTTPSRedirect(rule map[string]any) bool {
filters := nestedSlice(rule, "filters")
for _, rawFilter := range filters {
filter, ok := rawFilter.(map[string]any)
if !ok || stringValue(filter["type"]) != "RequestRedirect" {
continue
}
redirect, ok := filter["requestRedirect"].(map[string]any)
if ok && strings.EqualFold(stringValue(redirect["scheme"]), "https") {
return true
}
}
return false
}
func listenerAcceptsHTTPRoute(listener map[string]any, gatewayNamespace string, route kube.GenericResource, provider *kube.ResourceProvider) bool {
if !listenerHostnameIntersectsRoute(listener, route.Resource.Object) {
return false
}
allowedRoutes, ok := listener["allowedRoutes"].(map[string]any)
if !ok {
return route.ObjectMeta.GetNamespace() == gatewayNamespace
}
if kinds := nestedSlice(allowedRoutes, "kinds"); len(kinds) > 0 {
allowsHTTPRoute := false
for _, rawKind := range kinds {
kind, ok := rawKind.(map[string]any)
if ok && withDefaultString(stringValue(kind["group"]), gatewayAPIGroup) == gatewayAPIGroup && stringValue(kind["kind"]) == "HTTPRoute" {
allowsHTTPRoute = true
break
}
}
if !allowsHTTPRoute {
return false
}
}
namespaces, ok := allowedRoutes["namespaces"].(map[string]any)
if !ok || stringValue(namespaces["from"]) == "" || stringValue(namespaces["from"]) == "Same" {
return route.ObjectMeta.GetNamespace() == gatewayNamespace
}
if stringValue(namespaces["from"]) == "All" {
return true
}
if stringValue(namespaces["from"]) != "Selector" {
return false
}
selectorMap, ok := namespaces["selector"].(map[string]any)
if !ok {
return false
}
selector := &metav1.LabelSelector{}
selectorJSON, err := json.Marshal(selectorMap)
if err != nil {
return false
}
if err := json.Unmarshal(selectorJSON, selector); err != nil {
return false
}
compiled, err := metav1.LabelSelectorAsSelector(selector)
if err != nil {
return false
}
for _, namespace := range provider.Namespaces {
if namespace.Name == route.ObjectMeta.GetNamespace() {
return compiled.Matches(labels.Set(namespace.Labels))
}
}
return false
}
func listenerHostnameIntersectsRoute(listener, route map[string]any) bool {
listenerHostname := stringValue(listener["hostname"])
routeHostnames := nestedSlice(route, "spec", "hostnames")
if len(routeHostnames) == 0 || listenerHostname == "" {
return true
}
for _, routeHostname := range routeHostnames {
if hostnamesIntersect(listenerHostname, stringValue(routeHostname)) {
return true
}
}
return false
}
func hostnamesIntersect(left, right string) bool {
if left == "" || right == "" || left == "*" || right == "*" || strings.EqualFold(left, right) {
return true
}
leftSuffix, leftWildcard := strings.CutPrefix(strings.ToLower(left), "*.")
rightSuffix, rightWildcard := strings.CutPrefix(strings.ToLower(right), "*.")
switch {
case leftWildcard && rightWildcard:
return leftSuffix == rightSuffix || strings.HasSuffix(leftSuffix, "."+rightSuffix) || strings.HasSuffix(rightSuffix, "."+leftSuffix)
case leftWildcard:
return strings.HasSuffix(strings.ToLower(right), "."+leftSuffix)
case rightWildcard:
return strings.HasSuffix(strings.ToLower(left), "."+rightSuffix)
default:
return false
}
}
func httpRouteBackendRefs(object map[string]any) []objectReference {
var refs []objectReference
rules := nestedSlice(object, "spec", "rules")
for _, rawRule := range rules {
rule, ok := rawRule.(map[string]any)
if !ok {
continue
}
refs = append(refs, referencesAt(rule, "backendRefs")...)
filters := nestedSlice(rule, "filters")
for _, rawFilter := range filters {
filter, ok := rawFilter.(map[string]any)
if !ok || stringValue(filter["type"]) != "RequestMirror" {
continue
}
mirror, ok := filter["requestMirror"].(map[string]any)
if !ok {
continue
}
if backend, ok := mirror["backendRef"].(map[string]any); ok {
refs = append(refs, referenceFromMap(backend))
}
}
}
return refs
}
func referencesAt(object map[string]any, fields ...string) []objectReference {
items := nestedSlice(object, fields...)
if len(items) == 0 {
return nil
}
refs := make([]objectReference, 0, len(items))
for _, item := range items {
if ref, ok := item.(map[string]any); ok {
refs = append(refs, referenceFromMap(ref))
}
}
return refs
}
func referenceFromMap(ref map[string]any) objectReference {
return objectReference{
group: stringValue(ref["group"]),
kind: stringValue(ref["kind"]),
name: stringValue(ref["name"]),
namespace: stringValue(ref["namespace"]),
sectionName: stringValue(ref["sectionName"]),
port: int64Value(ref["port"]),
}
}
func withDefaults(ref objectReference, group, kind, namespace string) objectReference {
if ref.group == "" {
ref.group = group
}
if ref.kind == "" {
ref.kind = kind
}
if ref.namespace == "" {
ref.namespace = namespace
}
return ref
}
func withDefaultString(value, defaultValue string) string {
if value == "" {
return defaultValue
}
return value
}
func hasReferenceGrant(provider *kube.ResourceProvider, sourceNamespace, sourceKind string, target objectReference) bool {
for _, grant := range provider.Resources[gatewayAPIGroup+"/ReferenceGrant"] {
if grant.ObjectMeta.GetNamespace() != target.namespace {
continue
}
fromMatches := false
for _, from := range referencesAt(grant.Resource.Object, "spec", "from") {
if from.group == gatewayAPIGroup && from.kind == sourceKind && from.namespace == sourceNamespace {
fromMatches = true
break
}
}
if !fromMatches {
continue
}
for _, to := range referencesAt(grant.Resource.Object, "spec", "to") {
if to.group == target.group && to.kind == target.kind && (to.name == "" || to.name == target.name) {
return true
}
}
}
return false
}
func backendUsesTLS(provider *kube.ResourceProvider, ref objectReference) bool {
// ponytail: infer TLS from conventional ports and backend metadata; replace
// this with controller status or an implementation graph when Polaris has one.
if ref.port == 443 || ref.port == 8443 {
return true
}
groupKind := ref.kind
if ref.group != "" {
groupKind = ref.group + "/" + ref.kind
}
backend := findResource(provider.Resources[groupKind], ref.namespace, ref.name)
if backend == nil {
return false
}
if ref.group == "" && ref.kind == "Service" {
ports := nestedSlice(backend.Resource.Object, "spec", "ports")
for _, rawPort := range ports {
port, ok := rawPort.(map[string]any)
if !ok || (ref.port != 0 && int64Value(port["port"]) != ref.port) {
continue
}
name := strings.ToLower(stringValue(port["name"]))
appProtocol := strings.ToLower(stringValue(port["appProtocol"]))
if name == "https" || strings.HasPrefix(name, "https-") || appProtocol == "https" || strings.HasSuffix(appProtocol, "/https") {
return true
}
}
}
if ref.group == kgatewayAPIGroup && ref.kind == "Backend" {
hosts := nestedSlice(backend.Resource.Object, "spec", "static", "hosts")
for _, rawHost := range hosts {
host, ok := rawHost.(map[string]any)
if ok && (int64Value(host["port"]) == 443 || int64Value(host["port"]) == 8443) {
return true
}
}
}
return false
}
func hasBackendTLSPolicy(provider *kube.ResourceProvider, ref objectReference) bool {
if ref.group != "" || ref.kind != "Service" {
return false
}
for _, policy := range provider.Resources[gatewayAPIGroup+"/BackendTLSPolicy"] {
if policy.ObjectMeta.GetNamespace() == ref.namespace && policyTargets(policy, ref) {
return true
}
}
return false
}
func hasKgatewayBackendTLSPolicy(provider *kube.ResourceProvider, ref objectReference) bool {
for _, policy := range provider.Resources[kgatewayAPIGroup+"/BackendConfigPolicy"] {
if policy.ObjectMeta.GetNamespace() != ref.namespace {
continue
}
if _, found := nestedValue(policy.Resource.Object, "spec", "tls"); found && policyTargets(policy, ref) {
return true
}
}
return false
}
func policyTargets(policy kube.GenericResource, target objectReference) bool {
for _, ref := range referencesAt(policy.Resource.Object, "spec", "targetRefs") {
ref = withDefaults(ref, "", "Service", policy.ObjectMeta.GetNamespace())
if ref.group == target.group && ref.kind == target.kind && ref.name == target.name {
return true
}
}
return false
}
func findResource(resources []kube.GenericResource, namespace, name string) *kube.GenericResource {
for i := range resources {
if resources[i].ObjectMeta.GetNamespace() == namespace && resources[i].ObjectMeta.GetName() == name {
return &resources[i]
}
}
return nil
}
func stringValue(value any) string {
valueString, _ := value.(string)
return valueString
}
func int64Value(value any) int64 {
switch number := value.(type) {
case int:
return int64(number)
case int32:
return int64(number)
case int64:
return number
case float64:
return int64(number)
default:
return 0
}
}
func nestedSlice(object map[string]any, fields ...string) []any {
value, found := nestedValue(object, fields...)
if !found {
return nil
}
items, _ := value.([]any)
return items
}
func nestedValue(object map[string]any, fields ...string) (any, bool) {
var current any = object
for _, field := range fields {
currentMap, ok := current.(map[string]any)
if !ok {
return nil, false
}
current, ok = currentMap[field]
if !ok {
return nil, false
}
}
return current, true
}
func gatewayFailure(path, message string) (bool, []jsonschema.KeyError, error) {
return false, []jsonschema.KeyError{{
PropertyPath: path,
Message: message,
}}, nil
}
+2 -2
View File
@@ -311,8 +311,8 @@ func (res ResultSet) GetPrettyOutput() string {
if color.NoColor {
status = strings.Fields(status)[1] // remove emoji
}
str.WriteString(fmt.Sprintf("%s%s %s\n", indent, checkColor.Sprint(fillString(msg.ID, minIDLength-len(indent))), status))
str.WriteString(fmt.Sprintf("%s %s - %s\n", indent, msg.Category, msg.Message))
str.WriteString(indent + checkColor.Sprint(fillString(msg.ID, minIDLength-len(indent))) + " " + status + "\n")
str.WriteString(indent + " " + msg.Category + " - " + msg.Message + "\n")
}
return str.String()
}
@@ -28,7 +28,7 @@ import (
func TestGetTemplateInputReturnsPolarisSubKeys(t *testing.T) {
pod := test.MockPod() // Includes a container, required by GetPodSpec
pod.Spec.NodeName = "testNodeName"
pod.ObjectMeta.Name = "testpod"
pod.Name = "testpod"
genRes, err := kube.NewGenericResourceFromPod(pod, pod)
require.NoError(t, err, "creating new generic resource from a pod")
schemaTest := schemaTestCase{
+16 -24
View File
@@ -48,15 +48,15 @@ func (s schemaTestCase) ShortString() string {
var msg strings.Builder
targetStr := s.Target
if targetStr != "" {
msg.WriteString(fmt.Sprintf("target=%s, ", targetStr))
msg.WriteString("target=" + string(targetStr) + ", ")
}
ns := s.Resource.ObjectMeta.GetNamespace()
if ns != "" {
msg.WriteString(fmt.Sprintf("namespace=%s, ", ns))
msg.WriteString("namespace=" + ns + ", ")
}
msg.WriteString(fmt.Sprintf("resource=%s/%s", s.Resource.Kind, s.Resource.ObjectMeta.GetName()))
msg.WriteString("resource=" + s.Resource.Kind + "/" + s.Resource.ObjectMeta.GetName())
if s.Target == config.TargetContainer {
msg.WriteString(fmt.Sprintf(", container=%s", s.Container.Name))
msg.WriteString(", container=" + s.Container.Name)
}
return msg.String()
}
@@ -72,7 +72,7 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
check, ok = config.BuiltInChecks[checkID]
}
if !ok {
return nil, fmt.Errorf("Check %s not found", checkID)
return nil, fmt.Errorf("check %s not found", checkID)
}
containerName := ""
@@ -136,17 +136,12 @@ func getTemplateInput(test schemaTestCase) (map[string]any, error) {
}
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.KeyError) ResultMessage {
details := []string{}
for _, issue := range issues {
details = append(details, issue.Message)
}
result := ResultMessage{
ID: check.ID,
Severity: conf.Checks[check.ID],
Category: check.Category,
Success: passes,
// FIXME: need to fix the tests before adding this back
//Details: details,
// FIXME: need to fix the tests before adding Details from issues
}
if passes {
result.Message = check.SuccessMessage
@@ -167,17 +162,14 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
}
checkKey := fmt.Sprintf(exemptionAnnotationPattern, checkID)
val = annot[checkKey]
if strings.ToLower(val) == "true" {
return true
}
return false
return strings.ToLower(val) == "true"
}
// ApplyAllSchemaChecksToResourceProvider applies all available checks to a ResourceProvider
func ApplyAllSchemaChecksToResourceProvider(ctx context.Context, conf *config.Configuration, resourceProvider *kube.ResourceProvider) ([]Result, error) {
results := []Result{}
if resourceProvider == nil {
return nil, errors.New("No resource provider set, cannot apply schema checks")
return nil, errors.New("no resource provider set, cannot apply schema checks")
}
for _, resources := range resourceProvider.Resources {
kindResults, err := ApplyAllSchemaChecksToAllResources(ctx, conf, resourceProvider, resources)
@@ -241,7 +233,7 @@ func applyControllerSchemaChecks(ctx context.Context, conf *config.Configuration
}
for key, val := range nonControllerResults {
if _, ok := finalResult.Results[key]; ok {
return finalResult, errors.New("Duplicate finding for check " + key)
return finalResult, errors.New("duplicate finding for check " + key)
}
finalResult.Results[key] = val
}
@@ -351,7 +343,7 @@ func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID s
podCopy := *test.Resource.PodSpec
podCopy.InitContainers = []corev1.Container{}
podCopy.Containers = []corev1.Container{*test.Container}
containerIndex := -1
var containerIndex int
if !test.IsInitContainer {
containerIndex = funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
return value.Name == test.Container.Name
@@ -371,7 +363,7 @@ func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID s
}
passes, issues, err = check.CheckPodSpec(ctx, &podCopy)
} else {
return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
return nil, fmt.Errorf("unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
}
} else if check.Target == config.TargetPodSpec {
passes, issues, err = check.CheckPodSpec(ctx, test.Resource.PodSpec)
@@ -380,7 +372,7 @@ func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID s
passes, issues, err = check.CheckPodTemplate(ctx, test.Resource.PodTemplate)
prefix = getJSONSchemaPrefix(test.Resource.Kind)
} else if check.Target == config.TargetContainer {
containerIndex := -1
var containerIndex int
if !test.IsInitContainer {
containerIndex = funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
return value.Name == test.Container.Name
@@ -475,12 +467,12 @@ func deepCopyMutation(source config.Mutation) config.Mutation {
}
func getJSONSchemaPrefix(kind string) (prefix string) {
if kind == "CronJob" {
switch kind {
case "CronJob":
prefix = "/spec/jobTemplate/spec/template/spec"
} else if kind == "Pod" {
case "Pod":
prefix = "/spec"
} else if (kind == "Deployment") || (kind == "DaemonSet") ||
(kind == "StatefulSet") || (kind == "Job") || (kind == "ReplicationController") {
case "Deployment", "DaemonSet", "StatefulSet", "Job", "ReplicationController":
prefix = "/spec/template/spec"
}
return prefix
+3 -3
View File
@@ -50,7 +50,7 @@ func (cs *CountSummary) AddSummary(other CountSummary) {
// AddResult adds a single result to the summary
func (cs *CountSummary) AddResult(result ResultMessage) {
if result.Success == false {
if !result.Success {
if result.Severity == config.SeverityWarning {
cs.Warnings++
} else {
@@ -198,7 +198,7 @@ func (rs ResultSet) GetSuccesses() []ResultMessage {
func (rs ResultSet) GetWarnings() []ResultMessage {
warnings := []ResultMessage{}
for _, msg := range rs {
if msg.Success == false && msg.Severity == config.SeverityWarning {
if !msg.Success && msg.Severity == config.SeverityWarning {
warnings = append(warnings, msg)
}
}
@@ -209,7 +209,7 @@ func (rs ResultSet) GetWarnings() []ResultMessage {
func (rs ResultSet) GetDangers() []ResultMessage {
errors := []ResultMessage{}
for _, msg := range rs {
if msg.Success == false && msg.Severity == config.SeverityDanger {
if !msg.Success && msg.Severity == config.SeverityDanger {
errors = append(errors, msg)
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ func NewMutateWebhook(ctx context.Context, mgr manager.Manager, c config.Configu
}
func (m *Mutator) mutate(ctx context.Context, req admission.Request) ([]jsonpatch.Operation, error) {
results, kubeResources, err := GetValidatedResults(ctx, req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
results, kubeResources, err := GetValidatedResults(ctx, req.Kind.Kind, m.decoder, req, m.Config)
if err != nil {
logrus.Errorf("Error while validating resource: %v", err)
return nil, err
+13 -25
View File
@@ -17,7 +17,6 @@ package webhook
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
@@ -54,7 +53,7 @@ func NewValidateWebhook(mgr manager.Manager, c config.Configuration) {
}
func (v *Validator) handleInternal(ctx context.Context, req admission.Request) (*validator.Result, kube.GenericResource, error) {
return GetValidatedResults(ctx, req.AdmissionRequest.Kind.Kind, v.decoder, req, v.Config)
return GetValidatedResults(ctx, req.Kind.Kind, v.decoder, req, v.Config)
}
// GetValidatedResults returns the validated results.
@@ -72,31 +71,20 @@ func GetValidatedResults(ctx context.Context, kind string, decoder *admission.De
return nil, resource, err
}
if ownerReferences, ok := decoded["metadata"].(map[string]any)["ownerReferences"].([]any); ok && len(ownerReferences) > 0 {
allOwnersReferenceValid := true
dynamicClient, restMapper, _, _, err := kube.GetKubeClient(context.Background(), "")
if err != nil {
logrus.Errorf("getting the kubernetes client: %v", err)
return nil, resource, err
}
for _, ownerReference := range ownerReferences {
ownerReference := ownerReference.(map[string]any)
ctrl, err := kube.GetObject(context.Background(), req.Namespace, ownerReference["kind"].(string), ownerReference["apiVersion"].(string), ownerReference["name"].(string), dynamicClient, restMapper)
if err != nil {
logrus.Infof("error retrieving owner for object %s - running checks: %v", req.Name, err)
allOwnersReferenceValid = false
break
} else {
err = controller.ValidateIfControllerMatches(decoded, ctrl.Object)
if err != nil {
logrus.Infof("object %s has an owner but the owner is invalid - running checks: %v", req.Name, err)
allOwnersReferenceValid = false
break
}
}
if allOwnersReferenceValid {
logrus.Infof("object %s has owner(s) and the owner(s) are valid - skipping", req.Name)
return nil, resource, nil
}
ownerReference := ownerReferences[0].(map[string]any)
ctrl, err := kube.GetObject(context.Background(), req.Namespace, ownerReference["kind"].(string), ownerReference["apiVersion"].(string), ownerReference["name"].(string), dynamicClient, restMapper)
if err != nil {
logrus.Infof("error retrieving owner for object %s - running checks: %v", req.Name, err)
} else if err := controller.ValidateIfControllerMatches(decoded, ctrl.Object); err != nil {
logrus.Infof("object %s has an owner but the owner is invalid - running checks: %v", req.Name, err)
} else {
logrus.Infof("object %s has owner(s) and the owner(s) are valid - skipping", req.Name)
return nil, resource, nil
}
} else {
logrus.Infof("Object %s has no owner - running checks", req.Name)
@@ -141,7 +129,7 @@ func getFailureReason(result validator.Result) string {
for _, message := range result.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason.WriteString(fmt.Sprintf("- %s: %s\n", result.Kind, message.Message))
reason.WriteString("- " + result.Kind + ": " + message.Message + "\n")
}
}
@@ -149,14 +137,14 @@ func getFailureReason(result validator.Result) string {
if podResult != nil {
for _, message := range podResult.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason.WriteString(fmt.Sprintf("- Pod: %s\n", message.Message))
reason.WriteString("- Pod: " + message.Message + "\n")
}
}
for _, containerResult := range podResult.ContainerResults {
for _, message := range containerResult.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason.WriteString(fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message))
reason.WriteString("- Container " + containerResult.Name + ": " + message.Message + "\n")
}
}
}
+97
View File
@@ -0,0 +1,97 @@
// DO NOT EDIT - Managed by Terraform
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":dependencyDashboard"
],
"labels": ["dependencies"],
"prConcurrentLimit": 10,
"branchConcurrentLimit": 10,
"minimumReleaseAge": "7 days",
"enabledManagers": [
"gomod",
"dockerfile",
"circleci",
"custom.regex"
],
"postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths"],
"customManagers": [
{
"description": "HashiCorp Vault download URL in CircleCI",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"https://releases\\.hashicorp\\.com/vault/(?<currentValue>[\\d.]+)/vault_[\\d.]+_linux_amd64\\.zip"
],
"depNameTemplate": "hashicorp/vault",
"datasourceTemplate": "hashicorp-releases",
"autoReplaceStringTemplate": "https://releases.hashicorp.com/vault/{{{newValue}}}/vault_{{{newValue}}}_linux_amd64.zip"
},
{
"description": "HashiCorp Vault archive name in CircleCI (sha256 line must be updated manually)",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"vault_(?<currentValue>[\\d.]+)_linux_amd64\\.zip"
],
"depNameTemplate": "hashicorp/vault",
"datasourceTemplate": "hashicorp-releases",
"autoReplaceStringTemplate": "vault_{{{newValue}}}_linux_amd64.zip"
},
{
"description": "golangci-lint install.sh pin in CircleCI",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"golangci/golangci-lint/HEAD/install.sh \\| sh -s -- -b /usr/local/bin v(?<currentValue>[0-9.]+)"
],
"depNameTemplate": "golangci/golangci-lint",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v?(?<version>.*)$",
"autoReplaceStringTemplate": "golangci/golangci-lint/HEAD/install.sh | sh -s -- -b /usr/local/bin v{{{newValue}}}"
},
{
"description": "Kind node image for rok8s-scripts kubernetes_e2e_tests",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"kind_node_image:\\s*\"kindest/node:(?<currentValue>v[^\"\\s]+)\""
],
"depNameTemplate": "kindest/node",
"datasourceTemplate": "docker",
"autoReplaceStringTemplate": "kind_node_image: \"kindest/node:{{{newValue}}}\""
}
],
"packageRules": [
{
"description": "Group all non-major (minor + patch) updates into a single PR",
"matchUpdateTypes": ["minor", "patch", "digest", "lockFileMaintenance"],
"groupName": "all non-major dependencies",
"groupSlug": "all-non-major",
"minimumReleaseAge": "7 days"
},
{
"description": "Keep major updates separate and also enforce minimum age",
"matchUpdateTypes": ["major"],
"minimumReleaseAge": "7 days"
},
{
"description": "Disable replace updates",
"matchManagers": ["gomod"],
"matchDepTypes": ["replace"],
"enabled": false
},
{
"description": "Every upgrade: upstream release must be at least 7 days old",
"matchManagers": ["gomod", "dockerfile", "circleci", "custom.regex"],
"minimumReleaseAge": "7 days"
},
{
"description": "Group HashiCorp Vault version bumps (sha256 checksum still needs manual update)",
"groupName": "HashiCorp Vault",
"matchManagers": ["custom.regex"],
"matchDepNames": ["hashicorp/vault"]
}
]
}
@@ -0,0 +1,13 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: unrestricted
spec:
gatewayClassName: kgateway
listeners:
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All
@@ -0,0 +1,16 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: restricted
spec:
gatewayClassName: kgateway
listeners:
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"
@@ -0,0 +1,15 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: infra
spec:
gatewayClassName: kgateway
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
certificateRefs:
- name: wildcard
namespace: certificates
@@ -0,0 +1,30 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: infra
spec:
gatewayClassName: kgateway
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
certificateRefs:
- name: wildcard
namespace: certificates
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-infra-gateway
namespace: certificates
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: infra
to:
- group: ""
kind: Secret
name: wildcard
@@ -0,0 +1,15 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: fail-open
spec:
gatewayClassName: kgateway
tls:
frontend:
default:
validation:
mode: AllowInsecureFallback
listeners:
- name: https
protocol: HTTPS
port: 443
@@ -0,0 +1,15 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: fail-closed
spec:
gatewayClassName: kgateway
tls:
frontend:
default:
validation:
mode: AllowValidOnly
listeners:
- name: https
protocol: HTTPS
port: 443
@@ -0,0 +1,10 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: missing-tls
spec:
gatewayClassName: kgateway
listeners:
- name: https
protocol: HTTPS
port: 443
@@ -0,0 +1,13 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: insecure
spec:
gatewayClassName: kgateway
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs: []
@@ -0,0 +1,22 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: secure
spec:
gatewayClassName: kgateway
listeners:
- name: http
protocol: HTTP
port: 80
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: example-tls
- name: passthrough
protocol: TLS
port: 8443
tls:
mode: Passthrough
@@ -0,0 +1,12 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: plaintext-to-tls-backend
namespace: app
spec:
hostnames:
- app.example.com
rules:
- backendRefs:
- name: api
port: 443
@@ -0,0 +1,39 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: secure-kgateway-backend
namespace: app
spec:
hostnames:
- app.example.com
rules:
- backendRefs:
- group: gateway.kgateway.dev
kind: Backend
name: external-api
---
apiVersion: gateway.kgateway.dev/v1alpha1
kind: Backend
metadata:
name: external-api
namespace: app
spec:
type: Static
static:
hosts:
- host: api.example.com
port: 443
---
apiVersion: gateway.kgateway.dev/v1alpha1
kind: BackendConfigPolicy
metadata:
name: external-api-tls
namespace: app
spec:
targetRefs:
- group: gateway.kgateway.dev
kind: Backend
name: external-api
tls:
sni: api.example.com
wellKnownCACertificates: System
@@ -0,0 +1,26 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: secure-backend
namespace: app
spec:
hostnames:
- app.example.com
rules:
- backendRefs:
- name: api
port: 443
---
apiVersion: gateway.networking.k8s.io/v1
kind: BackendTLSPolicy
metadata:
name: api-tls
namespace: app
spec:
targetRefs:
- group: ""
kind: Service
name: api
validation:
hostname: api.app.svc.cluster.local
wellKnownCACertificates: System
@@ -0,0 +1,13 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shared-api
namespace: app
spec:
hostnames:
- app.example.com
rules:
- backendRefs:
- name: api
namespace: shared
port: 8080
@@ -0,0 +1,28 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shared-api
namespace: app
spec:
hostnames:
- app.example.com
rules:
- backendRefs:
- name: api
namespace: shared
port: 8080
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-app-route
namespace: shared
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: app
to:
- group: ""
kind: Service
name: api
@@ -0,0 +1,31 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: plaintext
namespace: app
spec:
parentRefs:
- name: public
namespace: infra
sectionName: http
hostnames:
- app.example.com
rules:
- backendRefs:
- name: app
port: 8080
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: infra
spec:
gatewayClassName: kgateway
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
@@ -0,0 +1,31 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: secure-host-only
namespace: app
spec:
parentRefs:
- name: public
hostnames:
- app.example.com
rules:
- backendRefs:
- name: app
port: 8080
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: app
spec:
gatewayClassName: kgateway
listeners:
- name: unrelated-http
hostname: other.example.com
protocol: HTTP
port: 80
- name: app-https
hostname: app.example.com
protocol: HTTPS
port: 443
@@ -0,0 +1,37 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: redirect
namespace: app
spec:
parentRefs:
- name: public
namespace: infra
sectionName: http
hostnames:
- app.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: infra
spec:
gatewayClassName: kgateway
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
@@ -0,0 +1,9 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: every-host
spec:
rules:
- backendRefs:
- name: api
port: 8080
@@ -0,0 +1,11 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: wildcard-host
spec:
hostnames:
- "*.example.com"
rules:
- backendRefs:
- name: api
port: 8080
@@ -0,0 +1,11 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: explicit-host
spec:
hostnames:
- api.example.com
rules:
- backendRefs:
- name: api
port: 8080
@@ -0,0 +1,11 @@
apiVersion: gateway.kgateway.dev/v1alpha1
kind: BackendConfigPolicy
metadata:
name: unverified
spec:
targetRefs:
- group: ""
kind: Service
name: api
tls:
insecureSkipVerify: true
@@ -0,0 +1,12 @@
apiVersion: gateway.kgateway.dev/v1alpha1
kind: BackendConfigPolicy
metadata:
name: verified
spec:
targetRefs:
- group: ""
kind: Service
name: api
tls:
sni: api.example.com
wellKnownCACertificates: System
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
set -euo pipefail
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
+3 -1
View File
@@ -188,7 +188,9 @@ func MockNamespace(name string) corev1.Namespace {
// SetupTestAPI creates a test kube API struct.
func SetupTestAPI(objects ...runtime.Object) (kubernetes.Interface, dynamic.Interface) {
scheme := runtime.NewScheme()
fake.AddToScheme(scheme)
if err := fake.AddToScheme(scheme); err != nil {
panic(err)
}
dynamicClient := dynamicFake.NewSimpleDynamicClient(scheme, objects...)
k := fake.NewSimpleClientset(objects...)
k.Resources = []*metav1.APIResourceList{
+1 -1
View File
@@ -89,7 +89,7 @@ func initTestCases() ([]testCase, map[string]string, map[string][]testCase) {
if err != nil {
panic(err)
}
resourceFilename := strings.Replace(tc.Name(), "mutated", "failure", -1)
resourceFilename := strings.ReplaceAll(tc.Name(), "mutated", "failure")
resources, err := kube.CreateResourceProviderFromPath(checkDir + "/" + resourceFilename)
if err != nil {