This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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: Create ephemeral K3s cluster
|
||||
run: |
|
||||
k3d cluster create --config local-quickstart/k3d-cluster-config.yaml
|
||||
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
|
||||
|
||||
@@ -30,7 +30,7 @@ It is not needed for the portfolio demo and is not run in CI.
|
||||
|
||||
- [x] Phase 1: local k3d definition and optional Terraform/Ansible path
|
||||
- [ ] Phase 2: ArgoCD app-of-apps deployment (scaffolded; runtime verification pending)
|
||||
- [ ] Phase 3: kube-bench CI scan
|
||||
- [x] Phase 3: kube-bench CI scan
|
||||
- [ ] Phase 4: Kyverno policy set and CIS mapping
|
||||
- [ ] Phase 5: Falco rules and webhook alerting
|
||||
- [ ] Phase 6: test workloads and evidence capture
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# CIS benchmark scanning
|
||||
|
||||
The scan uses the official kube-bench K3s profile, `k3s-cis-1.7`, against a
|
||||
disposable K3d control-plane node. kube-bench needs host PID access and
|
||||
read-only host mounts because the CIS checks inspect processes, permissions,
|
||||
and node configuration. The job is intentionally separate from the ArgoCD
|
||||
application tree so the scanner cannot become part of the workload baseline.
|
||||
|
||||
GitHub Actions creates the local cluster, runs the job, stores the JSON report,
|
||||
and prints pass, warn, and fail totals in the workflow summary. The workflow
|
||||
fails if the scanner job or report generation fails. It does not yet fail on
|
||||
CIS findings because a vanilla K3d cluster is expected to produce findings;
|
||||
later hardening phases can turn selected controls into merge gates.
|
||||
|
||||
To run the same scan locally against the active K3d cluster:
|
||||
|
||||
```bash
|
||||
kubectl create namespace kube-bench --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl delete job kube-bench -n kube-bench --ignore-not-found
|
||||
kubectl apply -f ci/kube-bench-k3s-job.yaml
|
||||
kubectl wait --for=condition=complete job/kube-bench -n kube-bench --timeout=180s
|
||||
kubectl logs -n kube-bench job/kube-bench > kube-bench-report.json
|
||||
```
|
||||
|
||||
The benchmark release is pinned in the Job image. Update it deliberately when
|
||||
the benchmark support matrix or K3s version changes.
|
||||
|
||||
## Gitea Actions support
|
||||
|
||||
Gitea Actions can run the same workflow file. Gitea's default workflow search
|
||||
order includes `.gitea/workflows` and `.github/workflows`; because this
|
||||
repository has no `.gitea/workflows` directory, Gitea will discover the
|
||||
existing `.github/workflows/kube-bench-scan.yml` file. GitHub Actions continues
|
||||
to use that same file.
|
||||
|
||||
The Gitea instance must have Actions enabled and an `act_runner` registered at
|
||||
the repository, organization, or instance level. The runner needs Docker
|
||||
access because the workflow creates a k3d cluster and runs the kube-bench Job.
|
||||
Register a dedicated runner for this repository rather than sharing a runner
|
||||
with unrelated repositories.
|
||||
|
||||
Example Docker runner setup, with the registration token supplied separately:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name kubernetes-security-baseline-runner \
|
||||
-e GITEA_INSTANCE_URL=https://git.swaphb.com/ \
|
||||
-e GITEA_RUNNER_REGISTRATION_TOKEN="$GITEA_RUNNER_REGISTRATION_TOKEN" \
|
||||
-e GITEA_RUNNER_NAME=kubernetes-security-baseline \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v gitea-runner-data:/data \
|
||||
docker.io/gitea/act_runner:latest
|
||||
```
|
||||
|
||||
Mounting the Docker socket gives workflow jobs substantial control over the
|
||||
runner host. Use a dedicated disposable runner, restrict its repository scope,
|
||||
and avoid placing unrelated credentials on that host.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: kube-bench
|
||||
namespace: kube-bench
|
||||
labels:
|
||||
app.kubernetes.io/name: kube-bench
|
||||
app.kubernetes.io/part-of: kubernetes-security-baseline
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kube-bench
|
||||
spec:
|
||||
# The K3s CIS profile checks host processes and node configuration.
|
||||
hostPID: true
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: "true"
|
||||
tolerations:
|
||||
- operator: Exists
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: kube-bench
|
||||
image: docker.io/aquasec/kube-bench:v0.16.0
|
||||
command:
|
||||
- kube-bench
|
||||
- run
|
||||
- --benchmark
|
||||
- k3s-cis-1.7
|
||||
- --json
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: etc-rancher-k3s
|
||||
mountPath: /etc/rancher/k3s
|
||||
readOnly: true
|
||||
- name: var-lib-rancher-k3s
|
||||
mountPath: /var/lib/rancher/k3s
|
||||
readOnly: true
|
||||
- name: var-lib-kubelet
|
||||
mountPath: /var/lib/kubelet
|
||||
readOnly: true
|
||||
- name: var-lib-cni
|
||||
mountPath: /var/lib/cni
|
||||
readOnly: true
|
||||
- name: etc-cni-netd
|
||||
mountPath: /etc/cni/net.d
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: etc-rancher-k3s
|
||||
hostPath:
|
||||
path: /etc/rancher/k3s
|
||||
- name: var-lib-rancher-k3s
|
||||
hostPath:
|
||||
path: /var/lib/rancher/k3s
|
||||
- name: var-lib-kubelet
|
||||
hostPath:
|
||||
path: /var/lib/kubelet
|
||||
- name: var-lib-cni
|
||||
hostPath:
|
||||
path: /var/lib/cni
|
||||
- name: etc-cni-netd
|
||||
hostPath:
|
||||
path: /etc/cni/net.d
|
||||
Reference in New Issue
Block a user