Files

154 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
Pod Security Admission Manager - Audit namespaces for PSA compliance,
apply labels, and generate migration reports.
"""
import json
import subprocess
import sys
import argparse
PSA_LABELS = {
"enforce": "pod-security.kubernetes.io/enforce",
"enforce-version": "pod-security.kubernetes.io/enforce-version",
"audit": "pod-security.kubernetes.io/audit",
"audit-version": "pod-security.kubernetes.io/audit-version",
"warn": "pod-security.kubernetes.io/warn",
"warn-version": "pod-security.kubernetes.io/warn-version",
}
SYSTEM_NAMESPACES = {
"kube-system", "kube-public", "kube-node-lease",
"calico-system", "tigera-operator", "gatekeeper-system", "falco",
}
def run_kubectl(args: list) -> str:
"""Execute kubectl command."""
cmd = ["kubectl"] + args
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout, result.stderr, result.returncode
def get_namespace_psa_labels() -> list:
"""Get PSA labels for all namespaces."""
stdout, _, _ = run_kubectl(["get", "namespaces", "-o", "json"])
data = json.loads(stdout)
results = []
for ns in data.get("items", []):
name = ns["metadata"]["name"]
labels = ns["metadata"].get("labels", {})
psa_info = {
"namespace": name,
"enforce": labels.get(PSA_LABELS["enforce"], "none"),
"audit": labels.get(PSA_LABELS["audit"], "none"),
"warn": labels.get(PSA_LABELS["warn"], "none"),
"is_system": name in SYSTEM_NAMESPACES,
}
results.append(psa_info)
return results
def dry_run_enforcement(namespace: str, level: str) -> dict:
"""Test what would happen if PSA enforcement was applied."""
stdout, stderr, rc = run_kubectl([
"label", "--dry-run=server", "--overwrite", "namespace", namespace,
f"pod-security.kubernetes.io/enforce={level}"
])
violations = []
if "Warning" in stderr or "Warning" in stdout:
for line in (stderr + stdout).split("\n"):
if "Warning" in line or "violate" in line:
violations.append(line.strip())
return {
"namespace": namespace,
"level": level,
"would_pass": rc == 0 and len(violations) == 0,
"violations": violations,
}
def audit_all_namespaces() -> list:
"""Audit all namespaces for PSA configuration."""
ns_info = get_namespace_psa_labels()
print("\n=== Pod Security Admission Audit ===\n")
print(f"{'Namespace':<30} {'Enforce':<12} {'Audit':<12} {'Warn':<12} {'System'}")
print("-" * 85)
compliant = 0
total = 0
for ns in ns_info:
if ns["is_system"]:
status = "EXEMPT"
elif ns["enforce"] == "restricted":
status = "COMPLIANT"
compliant += 1
elif ns["enforce"] == "baseline":
status = "PARTIAL"
else:
status = "NON-COMPLIANT"
if not ns["is_system"]:
total += 1
system = "Yes" if ns["is_system"] else "No"
print(f"{ns['namespace']:<30} {ns['enforce']:<12} {ns['audit']:<12} {ns['warn']:<12} {system}")
print(f"\n{compliant}/{total} non-system namespaces at restricted level")
return ns_info
def apply_psa_labels(namespace: str, level: str, version: str = "latest"):
"""Apply PSA labels to a namespace."""
labels = [
f"pod-security.kubernetes.io/enforce={level}",
f"pod-security.kubernetes.io/enforce-version={version}",
f"pod-security.kubernetes.io/audit=restricted",
f"pod-security.kubernetes.io/audit-version={version}",
f"pod-security.kubernetes.io/warn=restricted",
f"pod-security.kubernetes.io/warn-version={version}",
]
cmd = ["label", "--overwrite", "namespace", namespace] + labels
stdout, stderr, rc = run_kubectl(cmd)
if rc == 0:
print(f"Applied {level} PSA labels to {namespace}")
else:
print(f"Failed: {stderr}", file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description="Pod Security Admission Manager")
subparsers = parser.add_subparsers(dest="command")
subparsers.add_parser("audit", help="Audit namespace PSA labels")
test_cmd = subparsers.add_parser("test", help="Dry-run PSA enforcement")
test_cmd.add_argument("--namespace", "-n", required=True)
test_cmd.add_argument("--level", default="restricted", choices=["baseline", "restricted"])
apply_cmd = subparsers.add_parser("apply", help="Apply PSA labels")
apply_cmd.add_argument("--namespace", "-n", required=True)
apply_cmd.add_argument("--level", required=True, choices=["privileged", "baseline", "restricted"])
apply_cmd.add_argument("--version", default="latest")
args = parser.parse_args()
if args.command == "audit":
audit_all_namespaces()
elif args.command == "test":
result = dry_run_enforcement(args.namespace, args.level)
print(json.dumps(result, indent=2))
elif args.command == "apply":
apply_psa_labels(args.namespace, args.level, args.version)
else:
parser.print_help()
if __name__ == "__main__":
main()