Files
swarm-zap/skills/kubernetes/scripts/security-audit.sh

150 lines
5.6 KiB
Bash

#!/bin/bash
# security-audit.sh - Kubernetes security posture assessment
# Usage: ./security-audit.sh [namespace]
set -e
NAMESPACE=${1:-""}
NS_FLAG=""
if [ -n "$NAMESPACE" ]; then
NS_FLAG="-n $NAMESPACE"
echo "=== SECURITY AUDIT: Namespace $NAMESPACE ===" >&2
else
NS_FLAG="-A"
echo "=== SECURITY AUDIT: All Namespaces ===" >&2
fi
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" >&2
echo "" >&2
FINDINGS=()
CRITICAL=0
WARNING=0
INFO=0
# 1. Privileged Containers (Critical)
echo "### Checking for privileged containers..." >&2
PRIVILEGED=$(kubectl get pods $NS_FLAG -o json 2>/dev/null | jq -r '.items[] | select(.spec.containers[].securityContext.privileged == true) | "\(.metadata.namespace)/\(.metadata.name)"')
if [ -n "$PRIVILEGED" ]; then
CRITICAL=$((CRITICAL + 1))
FINDINGS+=("CRITICAL: Privileged containers found")
echo "CRITICAL: Privileged containers:" >&2
echo "$PRIVILEGED" >&2
else
echo "✓ No privileged containers" >&2
fi
# 2. Containers Running as Root (Warning)
echo -e "\n### Checking for root containers..." >&2
ROOT_CONTAINERS=$(kubectl get pods $NS_FLAG -o json 2>/dev/null | jq -r '.items[] | select(.spec.securityContext.runAsNonRoot != true) | select(.spec.containers[].securityContext.runAsNonRoot != true) | "\(.metadata.namespace)/\(.metadata.name)"' | sort -u)
ROOT_COUNT=$(echo "$ROOT_CONTAINERS" | grep -c . || echo 0)
if [ "$ROOT_COUNT" -gt 0 ]; then
WARNING=$((WARNING + 1))
FINDINGS+=("WARNING: $ROOT_COUNT pods may run as root")
echo "WARNING: Pods without runAsNonRoot:" >&2
echo "$ROOT_CONTAINERS" | head -10 >&2
[ "$ROOT_COUNT" -gt 10 ] && echo "... and $((ROOT_COUNT - 10)) more" >&2
else
echo "✓ All pods have runAsNonRoot" >&2
fi
# 3. Host Namespace Access (Critical)
echo -e "\n### Checking for host namespace access..." >&2
HOST_ACCESS=$(kubectl get pods $NS_FLAG -o json 2>/dev/null | jq -r '.items[] | select(.spec.hostNetwork == true or .spec.hostPID == true or .spec.hostIPC == true) | "\(.metadata.namespace)/\(.metadata.name)"')
if [ -n "$HOST_ACCESS" ]; then
CRITICAL=$((CRITICAL + 1))
FINDINGS+=("CRITICAL: Host namespace access detected")
echo "CRITICAL: Pods with host namespace access:" >&2
echo "$HOST_ACCESS" >&2
else
echo "✓ No host namespace access" >&2
fi
# 4. Missing Resource Limits (Warning)
echo -e "\n### Checking for missing resource limits..." >&2
NO_LIMITS=$(kubectl get pods $NS_FLAG -o json 2>/dev/null | jq -r '[.items[] | select(.spec.containers[].resources.limits == null)] | length')
if [ "$NO_LIMITS" -gt 10 ]; then
WARNING=$((WARNING + 1))
FINDINGS+=("WARNING: $NO_LIMITS containers without resource limits")
echo "WARNING: $NO_LIMITS containers missing resource limits" >&2
else
echo "✓ Resource limits configured ($NO_LIMITS missing)" >&2
fi
# 5. Default Service Account Usage (Info)
echo -e "\n### Checking for default service account usage..." >&2
DEFAULT_SA=$(kubectl get pods $NS_FLAG -o json 2>/dev/null | jq -r '.items[] | select(.spec.serviceAccountName == "default" or .spec.serviceAccountName == null) | "\(.metadata.namespace)/\(.metadata.name)"')
DEFAULT_SA_COUNT=$(echo "$DEFAULT_SA" | grep -c . || echo 0)
if [ "$DEFAULT_SA_COUNT" -gt 0 ]; then
INFO=$((INFO + 1))
FINDINGS+=("INFO: $DEFAULT_SA_COUNT pods using default service account")
echo "INFO: Pods using default SA:" >&2
echo "$DEFAULT_SA" | head -10 >&2
else
echo "✓ No pods using default service account" >&2
fi
# 6. Wildcard RBAC (Critical)
echo -e "\n### Checking for overly permissive RBAC..." >&2
WILDCARD_ROLES=$(kubectl get clusterroles -o json 2>/dev/null | jq -r '.items[] | select(.rules[]?.verbs[]? == "*" and .rules[]?.resources[]? == "*") | .metadata.name')
if [ -n "$WILDCARD_ROLES" ]; then
CRITICAL=$((CRITICAL + 1))
FINDINGS+=("CRITICAL: Wildcard RBAC permissions found")
echo "CRITICAL: ClusterRoles with wildcard permissions:" >&2
echo "$WILDCARD_ROLES" >&2
else
echo "✓ No wildcard RBAC permissions" >&2
fi
# 7. Pods without NetworkPolicy (Info)
echo -e "\n### Checking NetworkPolicy coverage..." >&2
if [ -n "$NAMESPACE" ]; then
NP_COUNT=$(kubectl get networkpolicy -n $NAMESPACE --no-headers 2>/dev/null | wc -l | tr -d ' ')
if [ "$NP_COUNT" -eq 0 ]; then
INFO=$((INFO + 1))
FINDINGS+=("INFO: Namespace $NAMESPACE has no NetworkPolicies")
echo "INFO: No NetworkPolicies in $NAMESPACE" >&2
else
echo "$NP_COUNT NetworkPolicies found" >&2
fi
else
NS_WITHOUT_NP=0
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do
count=$(kubectl get networkpolicy -n $ns --no-headers 2>/dev/null | wc -l | tr -d ' ')
[ "$count" -eq 0 ] && NS_WITHOUT_NP=$((NS_WITHOUT_NP + 1))
done
if [ "$NS_WITHOUT_NP" -gt 0 ]; then
INFO=$((INFO + 1))
FINDINGS+=("INFO: $NS_WITHOUT_NP namespaces without NetworkPolicies")
echo "INFO: $NS_WITHOUT_NP namespaces lack NetworkPolicies" >&2
fi
fi
# Summary
echo "" >&2
echo "========================================" >&2
echo "SECURITY AUDIT SUMMARY" >&2
echo "========================================" >&2
echo "Critical Issues: $CRITICAL" >&2
echo "Warnings: $WARNING" >&2
echo "Informational: $INFO" >&2
echo "" >&2
if [ ${#FINDINGS[@]} -gt 0 ]; then
echo "FINDINGS:" >&2
for finding in "${FINDINGS[@]}"; do
echo " - $finding" >&2
done
fi
# Output JSON
cat << EOF
{
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"namespace": "${NAMESPACE:-all}",
"critical": $CRITICAL,
"warning": $WARNING,
"info": $INFO,
"compliant": $([ $CRITICAL -eq 0 ] && echo "true" || echo "false")
}
EOF