name: Kube-bench CIS scan on: pull_request: push: branches: [main] permissions: contents: read jobs: kube-bench: name: Scan ephemeral K3s cluster runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Check out repository uses: actions/checkout@v4 - name: Install k3d run: curl -sfL https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash - name: Install kubectl shell: bash run: | set -euo pipefail kubectl_version=v1.35.5 temp_dir="$(mktemp -d)" trap 'rm -rf "$temp_dir"' EXIT curl --fail --location --retry 3 \ --output "$temp_dir/kubectl" \ "https://dl.k8s.io/release/${kubectl_version}/bin/linux/amd64/kubectl" curl --fail --location --retry 3 \ --output "$temp_dir/kubectl.sha256" \ "https://dl.k8s.io/release/${kubectl_version}/bin/linux/amd64/kubectl.sha256" printf '%s %s\n' "$(cat "$temp_dir/kubectl.sha256")" "$temp_dir/kubectl" | sha256sum --check --strict - install -m 0755 "$temp_dir/kubectl" /usr/local/bin/kubectl kubectl version --client - name: Create ephemeral K3s cluster run: | k3d cluster delete security-baseline-ci >/dev/null 2>&1 || true k3d cluster create --config ci/k3d-ci-cluster-config.yaml mkdir -p "$HOME/.kube" k3d kubeconfig get security-baseline-ci > "$HOME/.kube/config" api_host=127.0.0.1 if getent hosts host.docker.internal >/dev/null 2>&1; then api_host=host.docker.internal elif command -v docker >/dev/null 2>&1; then docker_gateway="$(docker network inspect bridge --format '{{(index .IPAM.Config 0).Gateway}}' 2>/dev/null || true)" if [[ -n "$docker_gateway" ]]; then api_host="$docker_gateway" fi fi if [[ "$api_host" == 127.0.0.1 && -r /proc/net/route ]]; then gateway_hex="$(awk '$2 == "00000000" {print $3; exit}' /proc/net/route)" if [[ "$gateway_hex" =~ ^[0-9A-Fa-f]{8}$ ]]; then api_host="$((16#${gateway_hex:6:2})).$((16#${gateway_hex:4:2})).$((16#${gateway_hex:2:2})).$((16#${gateway_hex:0:2}))" fi fi sed -i -E "s#https://0\.0\.0\.0:#https://${api_host}:#" "$HOME/.kube/config" kubectl wait --for=condition=Ready nodes --all --timeout=180s kubectl get nodes -o wide - name: Run kube-bench id: scan shell: bash run: | set +e mkdir -p artifacts kubectl create namespace kube-bench --dry-run=client -o yaml | kubectl apply -f - kubectl apply -f ci/kube-bench-k3s-job.yaml kubectl wait --for=condition=complete job/kube-bench -n kube-bench --timeout=180s job_rc=$? kubectl logs -n kube-bench job/kube-bench > artifacts/kube-bench-k3s-cis-1.7.json || true if ! jq -e . artifacts/kube-bench-k3s-cis-1.7.json >/dev/null; then echo "kube-bench did not produce valid JSON" >&2 cat artifacts/kube-bench-k3s-cis-1.7.json exit 1 fi pass_count=$(jq '[.. | objects | select(has("total_pass")) | .total_pass] | add // 0' artifacts/kube-bench-k3s-cis-1.7.json) warn_count=$(jq '[.. | objects | select(has("total_warn")) | .total_warn] | add // 0' artifacts/kube-bench-k3s-cis-1.7.json) fail_count=$(jq '[.. | objects | select(has("total_fail")) | .total_fail] | add // 0' artifacts/kube-bench-k3s-cis-1.7.json) { echo "## kube-bench CIS K3s 1.7" echo echo "| Result | Count |" echo "| --- | ---: |" echo "| Pass | ${pass_count} |" echo "| Warn | ${warn_count} |" echo "| Fail | ${fail_count} |" echo echo "Findings are reported as an artifact. The disposable K3d baseline is not a merge gate yet." } >> "$GITHUB_STEP_SUMMARY" exit "$job_rc" - name: Upload kube-bench report if: always() uses: actions/upload-artifact@v4 with: name: kube-bench-k3s-cis-1.7 path: artifacts/kube-bench-k3s-cis-1.7.json if-no-files-found: warn - name: Delete ephemeral cluster if: always() run: k3d cluster delete security-baseline-ci