#!/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 <