mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-29 22:17:16 +00:00
Compare commits
17
Commits
v10.2.1
...
js/gateway
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be3401cc9d | ||
|
|
4fb77d3b8c | ||
|
|
8f61e0c68a | ||
|
|
c2ccdb4a5d | ||
|
|
b860eb798b | ||
|
|
cdbb35f449 | ||
|
|
c0e23a233b | ||
|
|
3bfea9bf10 | ||
|
|
236ca14fca | ||
|
|
9fe4a72c77 | ||
|
|
dafa0b74fc | ||
|
|
f0a370c757 | ||
|
|
cb741e3eb1 | ||
|
|
b65142e8be | ||
|
|
31bc0fdb84 | ||
|
|
292a1c257c | ||
|
|
06ef2d3f70 |
+108
-207
@@ -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.2
|
||||
- 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.15.4
|
||||
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.15.4 \
|
||||
-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,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.23
|
||||
FROM alpine:3.24
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
|
||||
@@ -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"
|
||||
+18
-9
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Executable
+35
@@ -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
@@ -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
|
||||
@@ -9,16 +9,16 @@ 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.35.4
|
||||
k8s.io/apimachinery v0.36.2
|
||||
k8s.io/client-go v0.35.4
|
||||
sigs.k8s.io/controller-runtime v0.23.3
|
||||
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
|
||||
)
|
||||
|
||||
@@ -66,21 +66,21 @@ require (
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/net v0.55.0 // 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.45.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.35.4 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.36.0 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect
|
||||
)
|
||||
|
||||
@@ -67,8 +67,6 @@ github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvA
|
||||
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/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
|
||||
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -106,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.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
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=
|
||||
@@ -134,20 +132,20 @@ github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sirupsen/logrus v1.9.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=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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=
|
||||
@@ -156,31 +154,32 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
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=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||
@@ -195,27 +194,27 @@ 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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988=
|
||||
k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU=
|
||||
k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU=
|
||||
k8s.io/apiextensions-apiserver v0.35.4/go.mod h1:ogQlk+stIE8mnoRthSYCwlOS12fVqgWFiErMwPaXA7c=
|
||||
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.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8=
|
||||
k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY=
|
||||
k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo=
|
||||
k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw=
|
||||
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
|
||||
k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug=
|
||||
k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4=
|
||||
k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk=
|
||||
k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0=
|
||||
k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
|
||||
sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
|
||||
sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
|
||||
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/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=
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+26
-13
@@ -158,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]
|
||||
@@ -173,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
|
||||
@@ -198,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
|
||||
}
|
||||
@@ -248,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
|
||||
}
|
||||
@@ -322,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))
|
||||
}
|
||||
@@ -350,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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -176,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)
|
||||
}
|
||||
|
||||
@@ -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:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -62,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)
|
||||
@@ -86,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Executable
+11
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user