container security

Implementing Pod Security Admission Controller

Implement Kubernetes Pod Security Admission to enforce baseline and restricted security profiles at namespace level using built-in admission controller.

admission-controllerkubernetespod-security-admissionpod-security-standardspsa
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Pod Security Admission (PSA) is a built-in Kubernetes admission controller (stable since v1.25) that enforces Pod Security Standards at the namespace level. It replaces the deprecated PodSecurityPolicy (PSP) and provides three security profiles: Privileged, Baseline, and Restricted, with three enforcement modes: enforce, audit, and warn.

When to Use

  • When deploying or configuring implementing pod security admission controller capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Kubernetes v1.25+ (PSA is stable/GA)
  • kubectl with cluster-admin access
  • No dependency on external tools - PSA is built into kube-apiserver

Pod Security Standards

Privileged Profile

  • Unrestricted - No restrictions applied
  • Use case: System-level pods (kube-system, monitoring)

Baseline Profile

  • Minimally restrictive - Prevents known privilege escalation
  • Blocks: privileged containers, hostPID, hostIPC, hostNetwork, hostPorts, certain volume types, adding capabilities beyond runtime defaults

Restricted Profile

  • Heavily restricted - Follows security best practices
  • Requires: non-root, drop ALL capabilities, seccomp RuntimeDefault, read-only root filesystem considerations
  • Blocks: Everything in Baseline plus running as root, privilege escalation, non-approved volume types

Enforcement Modes

Mode Behavior Use Case
enforce Reject pods violating policy Production enforcement
audit Log violations to audit log Pre-enforcement assessment
warn Show warnings to user Developer feedback

Implementation

Apply to Namespace via Labels

# Restricted enforcement with audit and warn
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: v1.28
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.28
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.28
# Baseline enforcement for staging
apiVersion: v1
kind: Namespace
metadata:
  name: staging
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: v1.28
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.28
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.28
# Privileged for system namespaces
apiVersion: v1
kind: Namespace
metadata:
  name: kube-system
  labels:
    pod-security.kubernetes.io/enforce: privileged

Apply Labels with kubectl

# Set restricted enforcement
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=v1.28 \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted
 
# Set baseline enforcement
kubectl label namespace staging \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted
 
# Check current labels
kubectl get namespace production -o jsonpath='{.metadata.labels}' | jq .

Dry-Run Testing

# Test what would happen with restricted policy on a namespace
kubectl label --dry-run=server --overwrite namespace staging \
  pod-security.kubernetes.io/enforce=restricted
 
# Output shows existing pods that would violate the policy
# Warning: existing pods in namespace "staging" violate the new PodSecurity enforce level "restricted:latest"

Cluster-Wide Defaults (AdmissionConfiguration)

# /etc/kubernetes/psa-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
  - name: PodSecurity
    configuration:
      apiVersion: pod-security.admission.config.k8s.io/v1
      kind: PodSecurityConfiguration
      defaults:
        enforce: baseline
        enforce-version: latest
        audit: restricted
        audit-version: latest
        warn: restricted
        warn-version: latest
      exemptions:
        usernames: []
        runtimeClasses: []
        namespaces:
          - kube-system
          - kube-public
          - kube-node-lease
          - calico-system
          - gatekeeper-system
          - monitoring
          - falco

Apply to API Server

# Add to kube-apiserver manifests
# /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
  containers:
  - command:
    - kube-apiserver
    - --admission-control-config-file=/etc/kubernetes/psa-config.yaml
    volumeMounts:
    - name: psa-config
      mountPath: /etc/kubernetes/psa-config.yaml
      readOnly: true
  volumes:
  - name: psa-config
    hostPath:
      path: /etc/kubernetes/psa-config.yaml
      type: File

Compliant Pod Examples

Restricted-Compliant Pod

apiVersion: v1
kind: Pod
metadata:
  name: restricted-pod
  namespace: production
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  automountServiceAccountToken: false
  containers:
    - name: app
      image: myregistry/myapp:v1.0.0
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        limits:
          cpu: 500m
          memory: 256Mi
        requests:
          cpu: 100m
          memory: 128Mi
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir: {}

Baseline-Compliant Pod

apiVersion: v1
kind: Pod
metadata:
  name: baseline-pod
  namespace: staging
spec:
  containers:
    - name: app
      image: myregistry/myapp:v1.0.0
      securityContext:
        allowPrivilegeEscalation: false
      resources:
        limits:
          cpu: 500m
          memory: 256Mi

Migration from PodSecurityPolicy

Step 1: Audit Current State

# Check existing PSPs
kubectl get psp
 
# Check which service accounts use which PSP
kubectl get clusterrolebinding -o json | \
  jq '.items[] | select(.roleRef.name | startswith("psp-")) | {name: .metadata.name, subjects: .subjects}'

Step 2: Map PSP to PSA Profiles

# For each namespace, determine required PSA level
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
  echo "Namespace: $ns"
  kubectl label --dry-run=server namespace $ns \
    pod-security.kubernetes.io/enforce=restricted 2>&1 | head -5
done

Step 3: Apply PSA Labels (Audit First)

# Start with audit mode
kubectl label namespace production \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

Step 4: Review and Fix Violations

# Check audit logs for violations
kubectl get events --field-selector reason=FailedCreate -A

Step 5: Enable Enforcement

kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted

Monitoring

# Check PSA violations in events
kubectl get events --all-namespaces --field-selector reason=FailedCreate
 
# Check audit logs
kubectl logs -n kube-system kube-apiserver-* | grep "pod-security.kubernetes.io"
 
# List namespace PSA labels
kubectl get namespaces -L pod-security.kubernetes.io/enforce

Best Practices

  1. Start with audit+warn before enforce to assess impact
  2. Use dry-run to test enforcement before applying
  3. Exempt system namespaces (kube-system, monitoring) in cluster defaults
  4. Pin version (enforce-version) for predictable behavior across upgrades
  5. Set cluster-wide baseline as default, then restrict specific namespaces
  6. Combine with Gatekeeper for additional custom policies beyond PSA
  7. Use restricted profile for all production workloads
  8. Document exemptions with clear justification
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md5.1 KB

API Reference: Kubernetes Pod Security Admission Controller

Libraries Used

Library Purpose
kubernetes Official Kubernetes Python client for cluster API access
json Parse and format admission review payloads
yaml Read and write Pod Security Standard label configurations

Installation

pip install kubernetes pyyaml

Authentication

from kubernetes import client, config
 
# In-cluster (running inside a pod)
config.load_incluster_config()
 
# Local kubeconfig
config.load_kube_config(context="my-cluster")
 
v1 = client.CoreV1Api()

Pod Security Standards Levels

Level Description
privileged Unrestricted — no restrictions applied
baseline Minimally restrictive — prevents known privilege escalation
restricted Heavily restricted — follows hardening best practices

Namespace Label API

Pod Security Admission is configured via namespace labels:

Label Purpose
pod-security.kubernetes.io/enforce Reject pods that violate the policy
pod-security.kubernetes.io/enforce-version Pin policy to specific k8s version
pod-security.kubernetes.io/audit Log violations in audit log
pod-security.kubernetes.io/audit-version Pin audit policy version
pod-security.kubernetes.io/warn Show warnings to kubectl users
pod-security.kubernetes.io/warn-version Pin warning policy version

Core Operations

List Namespaces with PSA Labels

namespaces = v1.list_namespace()
for ns in namespaces.items:
    labels = ns.metadata.labels or {}
    enforce = labels.get("pod-security.kubernetes.io/enforce", "none")
    audit = labels.get("pod-security.kubernetes.io/audit", "none")
    warn = labels.get("pod-security.kubernetes.io/warn", "none")
    print(f"{ns.metadata.name}: enforce={enforce} audit={audit} warn={warn}")

Apply PSA Labels to a Namespace

body = {
    "metadata": {
        "labels": {
            "pod-security.kubernetes.io/enforce": "restricted",
            "pod-security.kubernetes.io/enforce-version": "latest",
            "pod-security.kubernetes.io/audit": "restricted",
            "pod-security.kubernetes.io/warn": "restricted",
        }
    }
}
v1.patch_namespace(name="production", body=body)

Audit All Namespaces for Missing PSA Labels

def audit_psa_labels():
    findings = []
    namespaces = v1.list_namespace()
    for ns in namespaces.items:
        name = ns.metadata.name
        labels = ns.metadata.labels or {}
        if name in ("kube-system", "kube-public", "kube-node-lease"):
            continue
        enforce = labels.get("pod-security.kubernetes.io/enforce")
        if not enforce:
            findings.append({"namespace": name, "issue": "no enforce label"})
        elif enforce == "privileged":
            findings.append({"namespace": name, "issue": "enforce=privileged"})
    return findings

Check Pod Violations Against a Level

def check_pod_security(namespace, level="restricted"):
    pods = v1.list_namespaced_pod(namespace=namespace)
    violations = []
    for pod in pods.items:
        for container in pod.spec.containers:
            sc = container.security_context
            if not sc:
                violations.append({
                    "pod": pod.metadata.name,
                    "container": container.name,
                    "issue": "no securityContext defined",
                })
                continue
            if sc.privileged:
                violations.append({
                    "pod": pod.metadata.name,
                    "container": container.name,
                    "issue": "privileged=true",
                })
            if sc.run_as_non_root is not True:
                violations.append({
                    "pod": pod.metadata.name,
                    "container": container.name,
                    "issue": "runAsNonRoot not set",
                })
            caps = sc.capabilities
            if level == "restricted" and (not caps or not caps.drop or "ALL" not in caps.drop):
                violations.append({
                    "pod": pod.metadata.name,
                    "container": container.name,
                    "issue": "capabilities.drop does not include ALL",
                })
    return violations

kubectl Equivalents

# Label a namespace with restricted enforcement
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/warn=restricted \
  --overwrite
 
# Dry-run to test impact before enforcing
kubectl label --dry-run=server --overwrite namespace production \
  pod-security.kubernetes.io/enforce=restricted
 
# Check which namespaces have PSA labels
kubectl get namespaces -L pod-security.kubernetes.io/enforce

Output Format

{
  "namespace": "production",
  "enforce_level": "restricted",
  "audit_level": "restricted",
  "warn_level": "restricted",
  "pod_violations": [
    {
      "pod": "legacy-app-7f8b9c",
      "container": "app",
      "issue": "privileged=true"
    }
  ],
  "compliant": false
}
standards.md1.1 KB

Standards - Pod Security Admission Controller

Kubernetes Pod Security Standards

Profile Controls Enforced
Baseline No privileged, no hostPID/IPC/Network, no hostPorts, restricted volumes, no procMount, restricted seccomp, restricted capabilities
Restricted All Baseline + non-root, drop ALL caps, seccomp required, restricted volume types, no privilege escalation

CIS Kubernetes Benchmark v1.8

  • 5.2.1: Ensure privileged containers are not used
  • 5.2.2-5.2.4: Ensure host namespace sharing is disabled
  • 5.2.5: Ensure privilege escalation is not allowed
  • 5.2.6: Ensure root containers are not admitted
  • 5.2.7: Ensure seccomp profile is set
  • 5.7.3: Apply security context to pods

NIST SP 800-190

  • Section 4.3: Container runtime security
  • Section 5.4: Admission control enforcement

NSA/CISA Kubernetes Hardening Guide v1.2

  • Section 1: Pod Security - Use Pod Security Standards

Compliance Mappings

  • PCI DSS v4.0 Req 2.2: Configuration standards
  • SOC 2 CC6.1: Logical access controls
  • HIPAA 164.312(a)(1): Access controls
workflows.md1.0 KB

Workflow - Implementing Pod Security Admission

Phase 1: Assessment

  1. List all namespaces and their current security posture
  2. Run dry-run against restricted profile for each namespace
  3. Document violations and required exemptions

Phase 2: Apply Audit Mode

for ns in production staging; do
  kubectl label namespace $ns \
    pod-security.kubernetes.io/audit=restricted \
    pod-security.kubernetes.io/warn=restricted
done

Phase 3: Fix Violations

  1. Update Deployments/StatefulSets with compliant security contexts
  2. Add seccomp profiles
  3. Switch containers to non-root
  4. Drop ALL capabilities

Phase 4: Enable Enforcement

kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=v1.28

Phase 5: Set Cluster Defaults

  1. Create AdmissionConfiguration with baseline defaults
  2. Apply to kube-apiserver
  3. Exempt system namespaces

Phase 6: Monitor

  1. Watch for FailedCreate events
  2. Review audit logs weekly
  3. Update exemptions as needed

Scripts 2

agent.py11.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Kubernetes Pod Security Admission audit agent.

Audits Kubernetes namespaces and workloads for Pod Security Standards (PSS)
compliance using kubectl. Checks namespace labels for enforce/audit/warn
modes, analyzes pod specs against Baseline and Restricted profiles, and
reports violations.
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime, timezone


def run_kubectl(args_list, timeout=60):
    """Execute a kubectl command and return parsed JSON output."""
    cmd = ["kubectl"] + args_list + ["-o", "json"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
    if result.returncode != 0:
        print(f"[!] kubectl error: {result.stderr[:200]}", file=sys.stderr)
        return None
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:
        return None


def get_namespaces():
    """Get all namespaces with their PSA labels."""
    print("[*] Fetching namespaces...")
    data = run_kubectl(["get", "namespaces"])
    if not data:
        return []
    namespaces = []
    for ns in data.get("items", []):
        name = ns.get("metadata", {}).get("name", "")
        labels = ns.get("metadata", {}).get("labels", {})
        psa_enforce = labels.get("pod-security.kubernetes.io/enforce", "")
        psa_audit = labels.get("pod-security.kubernetes.io/audit", "")
        psa_warn = labels.get("pod-security.kubernetes.io/warn", "")
        psa_enforce_version = labels.get("pod-security.kubernetes.io/enforce-version", "")
        namespaces.append({
            "name": name,
            "enforce": psa_enforce,
            "audit": psa_audit,
            "warn": psa_warn,
            "enforce_version": psa_enforce_version,
            "has_psa": bool(psa_enforce or psa_audit or psa_warn),
        })
    print(f"[+] Found {len(namespaces)} namespaces")
    return namespaces


def audit_namespace_psa(namespaces):
    """Audit PSA label configuration across namespaces."""
    findings = []
    system_ns = {"kube-system", "kube-public", "kube-node-lease", "default"}

    for ns in namespaces:
        name = ns["name"]
        if name in system_ns:
            continue

        if not ns["has_psa"]:
            findings.append({
                "namespace": name,
                "check": "No PSA labels configured",
                "severity": "HIGH",
                "recommendation": (
                    f"Apply PSA labels: kubectl label namespace {name} "
                    f"pod-security.kubernetes.io/enforce=baseline "
                    f"pod-security.kubernetes.io/warn=restricted"
                ),
            })
        elif ns["enforce"] == "privileged":
            findings.append({
                "namespace": name,
                "check": "PSA enforce set to privileged (permissive)",
                "severity": "HIGH",
                "recommendation": "Upgrade to baseline or restricted enforcement",
            })
        elif ns["enforce"] == "baseline" and not ns["warn"]:
            findings.append({
                "namespace": name,
                "check": "Enforce baseline but no warn=restricted",
                "severity": "MEDIUM",
                "recommendation": (
                    f"Add warn label: kubectl label namespace {name} "
                    f"pod-security.kubernetes.io/warn=restricted"
                ),
            })
        elif ns["enforce"] == "restricted":
            findings.append({
                "namespace": name,
                "check": "PSA enforce=restricted (most secure)",
                "severity": "INFO",
            })

    return findings


def audit_pod_security(namespace=None):
    """Audit pods for security spec violations against PSS profiles."""
    findings = []
    cmd = ["get", "pods", "--all-namespaces"] if not namespace else ["get", "pods", "-n", namespace]
    data = run_kubectl(cmd)
    if not data:
        return findings

    for pod in data.get("items", []):
        pod_name = pod.get("metadata", {}).get("name", "")
        pod_ns = pod.get("metadata", {}).get("namespace", "")
        spec = pod.get("spec", {})

        # Check host namespaces
        if spec.get("hostNetwork"):
            findings.append({
                "namespace": pod_ns, "pod": pod_name,
                "check": "hostNetwork enabled",
                "severity": "CRITICAL", "profile": "baseline",
            })
        if spec.get("hostPID"):
            findings.append({
                "namespace": pod_ns, "pod": pod_name,
                "check": "hostPID enabled",
                "severity": "CRITICAL", "profile": "baseline",
            })
        if spec.get("hostIPC"):
            findings.append({
                "namespace": pod_ns, "pod": pod_name,
                "check": "hostIPC enabled",
                "severity": "HIGH", "profile": "baseline",
            })

        containers = spec.get("containers", []) + spec.get("initContainers", [])
        for container in containers:
            c_name = container.get("name", "")
            sec_ctx = container.get("securityContext", {})

            # Privileged container
            if sec_ctx.get("privileged"):
                findings.append({
                    "namespace": pod_ns, "pod": pod_name, "container": c_name,
                    "check": "Privileged container",
                    "severity": "CRITICAL", "profile": "baseline",
                })

            # Run as root
            if sec_ctx.get("runAsUser") == 0:
                findings.append({
                    "namespace": pod_ns, "pod": pod_name, "container": c_name,
                    "check": "Running as root (UID 0)",
                    "severity": "HIGH", "profile": "restricted",
                })

            # Missing runAsNonRoot
            if not sec_ctx.get("runAsNonRoot"):
                findings.append({
                    "namespace": pod_ns, "pod": pod_name, "container": c_name,
                    "check": "runAsNonRoot not set",
                    "severity": "MEDIUM", "profile": "restricted",
                })

            # Writable root filesystem
            if not sec_ctx.get("readOnlyRootFilesystem"):
                findings.append({
                    "namespace": pod_ns, "pod": pod_name, "container": c_name,
                    "check": "Root filesystem not read-only",
                    "severity": "MEDIUM", "profile": "restricted",
                })

            # Privilege escalation
            if sec_ctx.get("allowPrivilegeEscalation", True):
                findings.append({
                    "namespace": pod_ns, "pod": pod_name, "container": c_name,
                    "check": "allowPrivilegeEscalation not disabled",
                    "severity": "MEDIUM", "profile": "restricted",
                })

            # Dangerous capabilities
            caps = sec_ctx.get("capabilities", {})
            added = caps.get("add", [])
            dangerous_caps = {"SYS_ADMIN", "NET_ADMIN", "SYS_PTRACE", "ALL",
                              "NET_RAW", "SYS_RAWIO", "SYS_MODULE"}
            for cap in added:
                if cap.upper() in dangerous_caps:
                    findings.append({
                        "namespace": pod_ns, "pod": pod_name, "container": c_name,
                        "check": f"Dangerous capability added: {cap}",
                        "severity": "CRITICAL" if cap.upper() in ("SYS_ADMIN", "ALL") else "HIGH",
                        "profile": "baseline",
                    })

            # Drop ALL capabilities check (restricted)
            dropped = [c.upper() for c in caps.get("drop", [])]
            if "ALL" not in dropped:
                findings.append({
                    "namespace": pod_ns, "pod": pod_name, "container": c_name,
                    "check": "Capabilities not dropping ALL",
                    "severity": "LOW", "profile": "restricted",
                })

            # Host ports
            for port in container.get("ports", []):
                if port.get("hostPort"):
                    findings.append({
                        "namespace": pod_ns, "pod": pod_name, "container": c_name,
                        "check": f"hostPort exposed: {port['hostPort']}",
                        "severity": "HIGH", "profile": "baseline",
                    })

    return findings


def format_summary(ns_findings, pod_findings, namespaces):
    """Print audit summary."""
    print(f"\n{'='*60}")
    print(f"  Pod Security Admission Audit Report")
    print(f"{'='*60}")
    print(f"  Namespaces      : {len(namespaces)}")
    psa_configured = sum(1 for ns in namespaces if ns["has_psa"])
    print(f"  PSA Configured  : {psa_configured}/{len(namespaces)}")
    print(f"  NS Findings     : {len(ns_findings)}")
    print(f"  Pod Findings    : {len(pod_findings)}")

    all_findings = ns_findings + pod_findings
    severity_counts = {}
    for f in all_findings:
        sev = f.get("severity", "INFO")
        severity_counts[sev] = severity_counts.get(sev, 0) + 1

    print(f"\n  By Severity:")
    for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
        count = severity_counts.get(sev, 0)
        if count > 0:
            print(f"    {sev:10s}: {count}")

    print(f"\n  Namespace PSA Status:")
    for ns in namespaces:
        if ns["name"] in ("kube-system", "kube-public", "kube-node-lease"):
            continue
        status = ns["enforce"] or "none"
        print(f"    {ns['name']:30s}: enforce={status:12s} audit={ns['audit'] or 'none':12s}")

    if pod_findings:
        print(f"\n  Top Pod Violations:")
        for f in pod_findings[:15]:
            if f["severity"] in ("CRITICAL", "HIGH"):
                print(f"    [{f['severity']:8s}] {f.get('namespace', '')}/"
                      f"{f.get('pod', '')}: {f['check']}")

    return severity_counts


def main():
    parser = argparse.ArgumentParser(
        description="Kubernetes Pod Security Admission audit agent"
    )
    parser.add_argument("--namespace", "-n", help="Specific namespace to audit (default: all)")
    parser.add_argument("--skip-pods", action="store_true", help="Only audit namespace labels")
    parser.add_argument("--kubeconfig", help="Path to kubeconfig file")
    parser.add_argument("--context", help="Kubernetes context to use")
    parser.add_argument("--output", "-o", help="Output JSON report path")
    parser.add_argument("--verbose", "-v", action="store_true")
    args = parser.parse_args()

    if args.kubeconfig:
        os.environ["KUBECONFIG"] = args.kubeconfig

    namespaces = get_namespaces()
    ns_findings = audit_namespace_psa(namespaces)

    pod_findings = []
    if not args.skip_pods:
        pod_findings = audit_pod_security(args.namespace)

    severity_counts = format_summary(ns_findings, pod_findings, namespaces)

    report = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "tool": "PSA Audit",
        "namespaces": namespaces,
        "namespace_findings": ns_findings,
        "pod_findings": pod_findings,
        "severity_counts": severity_counts,
        "risk_level": (
            "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
            else "HIGH" if severity_counts.get("HIGH", 0) > 0
            else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0
            else "LOW"
        ),
    }

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"\n[+] Report saved to {args.output}")
    elif args.verbose:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
process.py5.1 KB
Display-only source. This catalog never executes bundled scripts.
#!/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()

Assets 1

template.mdtext/markdown · 1.3 KB
Keep exploring