Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

157 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Kubernetes Network Policy Agent - audits pod-to-pod communication and network policy coverage."""
import json
import argparse
import logging
import subprocess
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def kubectl_json(args_list):
"""Execute kubectl command and return JSON output."""
cmd = ["kubectl"] + args_list + ["-o", "json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return json.loads(result.stdout) if result.returncode == 0 else {}
def get_namespaces():
return kubectl_json(["get", "namespaces"])
def get_network_policies(namespace="--all-namespaces"):
if namespace == "--all-namespaces":
return kubectl_json(["get", "networkpolicies", "--all-namespaces"])
return kubectl_json(["get", "networkpolicies", "-n", namespace])
def get_pods(namespace="--all-namespaces"):
if namespace == "--all-namespaces":
return kubectl_json(["get", "pods", "--all-namespaces"])
return kubectl_json(["get", "pods", "-n", namespace])
def analyze_policy_coverage(policies, pods):
"""Determine which pods are covered by network policies."""
policy_selectors = []
for item in policies.get("items", []):
ns = item.get("metadata", {}).get("namespace", "")
spec = item.get("spec", {})
selector = spec.get("podSelector", {}).get("matchLabels", {})
policy_types = spec.get("policyTypes", [])
policy_selectors.append({
"namespace": ns,
"selector": selector,
"ingress": "Ingress" in policy_types or spec.get("ingress") is not None,
"egress": "Egress" in policy_types or spec.get("egress") is not None,
})
covered_pods = set()
uncovered_pods = []
for pod in pods.get("items", []):
pod_ns = pod.get("metadata", {}).get("namespace", "")
pod_name = pod.get("metadata", {}).get("name", "")
pod_labels = pod.get("metadata", {}).get("labels", {})
is_covered = False
for policy in policy_selectors:
if policy["namespace"] != pod_ns:
continue
if not policy["selector"]:
is_covered = True
break
if all(pod_labels.get(k) == v for k, v in policy["selector"].items()):
is_covered = True
break
if is_covered:
covered_pods.add(f"{pod_ns}/{pod_name}")
else:
uncovered_pods.append({"namespace": pod_ns, "pod": pod_name, "labels": pod_labels})
total = len(pods.get("items", []))
return {
"total_pods": total,
"covered_pods": len(covered_pods),
"uncovered_pods": len(uncovered_pods),
"coverage_percent": round(len(covered_pods) / max(total, 1) * 100, 1),
"uncovered_pod_list": uncovered_pods[:20],
}
def detect_overly_permissive_policies(policies):
"""Find network policies that allow all traffic."""
findings = []
for item in policies.get("items", []):
name = item.get("metadata", {}).get("name", "")
ns = item.get("metadata", {}).get("namespace", "")
spec = item.get("spec", {})
if not spec.get("podSelector", {}).get("matchLabels"):
ingress_rules = spec.get("ingress", [])
for rule in ingress_rules:
if not rule.get("from"):
findings.append({"policy": name, "namespace": ns,
"issue": "allows_all_ingress", "severity": "high"})
egress_rules = spec.get("egress", [])
for rule in egress_rules:
if not rule.get("to"):
findings.append({"policy": name, "namespace": ns,
"issue": "allows_all_egress", "severity": "medium"})
return findings
def analyze_namespace_isolation(policies, namespaces):
"""Check which namespaces have default-deny policies."""
ns_with_deny = set()
for item in policies.get("items", []):
spec = item.get("spec", {})
if (not spec.get("podSelector", {}).get("matchLabels") and
not spec.get("ingress") and "Ingress" in spec.get("policyTypes", [])):
ns_with_deny.add(item.get("metadata", {}).get("namespace", ""))
all_ns = [ns.get("metadata", {}).get("name", "") for ns in namespaces.get("items", [])]
system_ns = {"kube-system", "kube-public", "kube-node-lease"}
user_ns = [ns for ns in all_ns if ns not in system_ns]
return {
"total_namespaces": len(user_ns),
"namespaces_with_default_deny": len(ns_with_deny),
"isolation_percent": round(len(ns_with_deny) / max(len(user_ns), 1) * 100, 1),
"unprotected_namespaces": [ns for ns in user_ns if ns not in ns_with_deny],
}
def generate_report(policies, pods, namespaces):
coverage = analyze_policy_coverage(policies, pods)
permissive = detect_overly_permissive_policies(policies)
isolation = analyze_namespace_isolation(policies, namespaces)
report = {
"timestamp": datetime.utcnow().isoformat(),
"total_network_policies": len(policies.get("items", [])),
"pod_coverage": coverage,
"namespace_isolation": isolation,
"overly_permissive_policies": permissive,
"total_findings": len(permissive),
}
return report
def main():
parser = argparse.ArgumentParser(description="Kubernetes Network Policy Audit Agent")
parser.add_argument("--namespace", default="--all-namespaces", help="Namespace to audit")
parser.add_argument("--output", default="k8s_netpol_report.json")
args = parser.parse_args()
policies = get_network_policies(args.namespace)
pods = get_pods(args.namespace)
namespaces = get_namespaces()
report = generate_report(policies, pods, namespaces)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("K8s NetPol: %.1f%% pod coverage, %.1f%% namespace isolation, %d findings",
report["pod_coverage"]["coverage_percent"],
report["namespace_isolation"]["isolation_percent"],
report["total_findings"])
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()