150 lines
5.6 KiB
Bash
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
|