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
|
- [x] Phase 1: local k3d definition and optional Terraform/Ansible path
|
||||||
- [ ] Phase 2: ArgoCD app-of-apps deployment (scaffolded; runtime verification pending)
|
- [ ] 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 4: Kyverno policy set and CIS mapping
|
||||||
- [ ] Phase 5: Falco rules and webhook alerting
|
- [ ] Phase 5: Falco rules and webhook alerting
|
||||||
- [ ] Phase 6: test workloads and evidence capture
|
- [ ] 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