Add Kubernetes attack path drill
This commit is contained in:
parent
8964fda2fd
commit
5f8fdfef3d
|
|
@ -167,6 +167,10 @@ user agents, auth failures, and HTTP errors.
|
|||
: Check the Tetragon runtime-detection DaemonSet and print the command for
|
||||
watching recent events.
|
||||
|
||||
`security-attack-path [namespace] [workload]`
|
||||
: Run a read-only compromise-path drill for a Kubernetes workload. Defaults to
|
||||
the public website deployment.
|
||||
|
||||
Kyverno hardening policies
|
||||
: `apps/supply-chain-policy` includes audit-mode checks for privileged pods,
|
||||
privilege escalation, hostPath use, resource requests/limits, and mutable image
|
||||
|
|
|
|||
10
jeannie
10
jeannie
|
|
@ -5829,6 +5829,11 @@ Useful practice trigger:
|
|||
EOF
|
||||
}
|
||||
|
||||
security_attack_path() {
|
||||
require_debian_server "security-attack-path"
|
||||
"${REPO_ROOT}/scripts/security-attack-path" "${@:2}"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
up)
|
||||
up
|
||||
|
|
@ -5963,6 +5968,9 @@ case "${1:-}" in
|
|||
security-runtime)
|
||||
security_runtime
|
||||
;;
|
||||
security-attack-path)
|
||||
security_attack_path "$@"
|
||||
;;
|
||||
openwrt)
|
||||
openwrt
|
||||
;;
|
||||
|
|
@ -5974,7 +5982,7 @@ case "${1:-}" in
|
|||
echo "Log: ${JEANNIE_LOG_FILE}"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|openwrt|nuke}"
|
||||
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|security-attack-path|openwrt|nuke}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
KUBECONFIG="${KUBECONFIG:-${HOME}/.kube/config}"
|
||||
ATTACK_NAMESPACE="${1:-${SECURITY_ATTACK_NAMESPACE:-website-production}}"
|
||||
ATTACK_WORKLOAD="${2:-${SECURITY_ATTACK_WORKLOAD:-deployment/php-website-deployment}}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/security-attack-path [namespace] [workload]
|
||||
|
||||
Default:
|
||||
scripts/security-attack-path website-production deployment/php-website-deployment
|
||||
|
||||
This is a read-only compromise-path drill. It inspects what a compromised pod
|
||||
would inherit: service account, mounted secrets, RBAC bindings, network
|
||||
policies, and nearby services.
|
||||
EOF
|
||||
}
|
||||
|
||||
require_kubectl() {
|
||||
if ! command -v kubectl >/dev/null 2>&1; then
|
||||
echo "kubectl is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "${KUBECONFIG}" ]]; then
|
||||
echo "KUBECONFIG does not exist: ${KUBECONFIG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
section() {
|
||||
printf '\n== %s ==\n' "$1"
|
||||
}
|
||||
|
||||
kubectl_ro() {
|
||||
kubectl --kubeconfig "${KUBECONFIG}" "$@"
|
||||
}
|
||||
|
||||
jsonpath_or_unknown() {
|
||||
local resource="$1"
|
||||
local path="$2"
|
||||
local value
|
||||
|
||||
value="$(kubectl_ro -n "${ATTACK_NAMESPACE}" get "${resource}" -o "jsonpath=${path}" 2>/dev/null || true)"
|
||||
printf '%s\n' "${value:-unknown}"
|
||||
}
|
||||
|
||||
main() {
|
||||
local service_account
|
||||
local automount
|
||||
local selector
|
||||
|
||||
case "${1:-}" in
|
||||
-h | --help | help)
|
||||
usage
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
require_kubectl
|
||||
|
||||
section "Attack Path Drill"
|
||||
printf 'Namespace: %s\n' "${ATTACK_NAMESPACE}"
|
||||
printf 'Workload: %s\n' "${ATTACK_WORKLOAD}"
|
||||
|
||||
if ! kubectl_ro -n "${ATTACK_NAMESPACE}" get "${ATTACK_WORKLOAD}" >/dev/null 2>&1; then
|
||||
echo "Target workload not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
section "Identity"
|
||||
service_account="$(jsonpath_or_unknown "${ATTACK_WORKLOAD}" '{.spec.template.spec.serviceAccountName}')"
|
||||
automount="$(jsonpath_or_unknown "${ATTACK_WORKLOAD}" '{.spec.template.spec.automountServiceAccountToken}')"
|
||||
service_account="${service_account:-default}"
|
||||
printf 'ServiceAccount: %s\n' "${service_account}"
|
||||
printf 'Automount token: %s\n' "${automount}"
|
||||
if [[ "${automount}" != "false" ]]; then
|
||||
printf 'Focus: pod may receive a Kubernetes API token unless blocked by defaults.\n'
|
||||
fi
|
||||
|
||||
section "Mounted Secrets And Config"
|
||||
kubectl_ro -n "${ATTACK_NAMESPACE}" get "${ATTACK_WORKLOAD}" \
|
||||
-o jsonpath='{range .spec.template.spec.volumes[*]}{.name}{" secret="}{.secret.secretName}{" configMap="}{.configMap.name}{" pvc="}{.persistentVolumeClaim.claimName}{" hostPath="}{.hostPath.path}{"\n"}{end}' || true
|
||||
|
||||
section "RBAC Bindings In Namespace"
|
||||
kubectl_ro -n "${ATTACK_NAMESPACE}" get rolebinding -o wide 2>/dev/null || true
|
||||
printf '\nClusterRoleBindings mentioning service account %s/%s:\n' "${ATTACK_NAMESPACE}" "${service_account}"
|
||||
kubectl_ro get clusterrolebinding -o jsonpath='{range .items[?(@.subjects)]}{.metadata.name}{" "}{range .subjects[*]}{.kind}{"/"}{.namespace}{"/"}{.name}{" "}{end}{"\n"}{end}' 2>/dev/null |
|
||||
grep -F "ServiceAccount/${ATTACK_NAMESPACE}/${service_account}" || true
|
||||
|
||||
section "Network Policies"
|
||||
kubectl_ro -n "${ATTACK_NAMESPACE}" get networkpolicy -o wide 2>/dev/null || true
|
||||
if ! kubectl_ro -n "${ATTACK_NAMESPACE}" get networkpolicy >/dev/null 2>&1; then
|
||||
printf 'Focus: no NetworkPolicy objects found; pod egress is probably unrestricted by namespace policy.\n'
|
||||
fi
|
||||
|
||||
section "Nearby Services"
|
||||
kubectl_ro -n "${ATTACK_NAMESPACE}" get svc,endpoints -o wide 2>/dev/null || true
|
||||
|
||||
section "Selected Pods"
|
||||
# shellcheck disable=SC2016
|
||||
selector="$(kubectl_ro -n "${ATTACK_NAMESPACE}" get "${ATTACK_WORKLOAD}" -o go-template='{{range $k,$v := .spec.selector.matchLabels}}{{printf "%s=%s," $k $v}}{{end}}' 2>/dev/null | sed 's/,$//')"
|
||||
if [[ -n "${selector}" ]]; then
|
||||
kubectl_ro -n "${ATTACK_NAMESPACE}" get pods -l "${selector}" -o wide
|
||||
else
|
||||
kubectl_ro -n "${ATTACK_NAMESPACE}" get pods -o wide
|
||||
fi
|
||||
|
||||
section "Manual Follow-Up"
|
||||
cat <<EOF
|
||||
Ask these questions before adding a fix:
|
||||
- Does the pod need a service account token?
|
||||
- Are any mounted Secrets unnecessary for runtime?
|
||||
- Can this namespace use default-deny egress with explicit allows?
|
||||
- Can the workload run as non-root with allowPrivilegeEscalation=false?
|
||||
- Should this namespace be isolated from Gitea, registry, Pi-hole, and Kubernetes API?
|
||||
EOF
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -14,6 +14,7 @@ Use Jeannie from the Debian homelab host:
|
|||
./jeannie security-web
|
||||
./jeannie security-logs
|
||||
./jeannie security-runtime
|
||||
./jeannie security-attack-path
|
||||
```
|
||||
|
||||
The default `security-scan` runs:
|
||||
|
|
@ -38,6 +39,8 @@ The focused commands are:
|
|||
- `security-logs`: read-only summary of recent Gitea and Traefik security
|
||||
signals.
|
||||
- `security-runtime`: Tetragon rollout check and event-log helper.
|
||||
- `security-attack-path`: read-only compromise-path drill for a Kubernetes
|
||||
workload.
|
||||
|
||||
Reports are written to:
|
||||
|
||||
|
|
@ -75,3 +78,10 @@ to learn what container process execution looks like after a pod is compromised.
|
|||
Kyverno hardening policies live under `apps/supply-chain-policy`. The added pod
|
||||
hardening rules start in Audit mode so you can review violations before
|
||||
promoting any rule to Deny.
|
||||
|
||||
`security-attack-path` defaults to the public website workload and can target a
|
||||
different workload:
|
||||
|
||||
```sh
|
||||
./jeannie security-attack-path security-lab deployment/juice-shop
|
||||
```
|
||||
|
|
|
|||
Loading…
Reference in New Issue