Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
Overview
Pod Security Standards (PSS) define three levels of security policies -- Privileged, Baseline, and Restricted -- enforced by the Pod Security Admission (PSA) controller built into Kubernetes 1.25+. PSA replaces the deprecated PodSecurityPolicy and provides namespace-level enforcement with three modes: enforce, audit, and warn.
When to Use
- When deploying or configuring implementing kubernetes pod security standards 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 cluster 1.25+ (PSA GA)
- kubectl configured with cluster-admin access
- Understanding of Linux capabilities and security contexts
Core Concepts
Three Security Profiles
| Profile | Purpose | Restrictions |
|---|---|---|
| Privileged | Unrestricted, system workloads | None |
| Baseline | Prevents known escalations | No hostNetwork, hostPID, hostIPC, privileged containers, dangerous capabilities |
| Restricted | Hardened best practices | Non-root, drop ALL caps, seccomp required, read-only rootfs recommended |
Three Enforcement Modes
| Mode | Behavior |
|---|---|
| enforce | Rejects pods that violate the policy |
| audit | Logs violations in audit log but allows pod |
| warn | Returns warning to user but allows pod |
Workflow
Step 1: Label Namespaces for PSA
# Restricted namespace - production workloads
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest# Baseline namespace - general workloads
apiVersion: v1
kind: Namespace
metadata:
name: staging
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest# Privileged namespace - system components only
apiVersion: v1
kind: Namespace
metadata:
name: kube-system
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/enforce-version: latestStep 2: Apply Labels to Existing Namespaces
# Apply restricted enforcement to production
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted \
--overwrite
# Apply baseline to staging with restricted warnings
kubectl label namespace staging \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted \
--overwrite
# Check labels on all namespaces
kubectl get namespaces -L pod-security.kubernetes.io/enforceStep 3: Create Compliant Pod Specs
# Restricted-compliant deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myregistry.com/myapp:v1.0.0@sha256:abc123
ports:
- containerPort: 8080
protocol: TCP
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 65534
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache
volumes:
- name: tmp
emptyDir:
sizeLimit: 100Mi
- name: cache
emptyDir:
sizeLimit: 50MiStep 4: Gradual Migration Strategy
# Phase 1: Audit mode - discover violations without blocking
kubectl label namespace my-namespace \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
# Check audit logs for violations
kubectl logs -n kube-system -l component=kube-apiserver | grep "pod-security"
# Phase 2: Enforce baseline, warn on restricted
kubectl label namespace my-namespace \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/warn=restricted \
--overwrite
# Phase 3: Full restricted enforcement
kubectl label namespace my-namespace \
pod-security.kubernetes.io/enforce=restricted \
--overwriteStep 5: Dry-Run Enforcement Testing
# Test what would happen with restricted enforcement
kubectl label --dry-run=server --overwrite namespace my-namespace \
pod-security.kubernetes.io/enforce=restricted
# Example output:
# Warning: existing pods in namespace "my-namespace" violate the new
# PodSecurity enforce level "restricted:latest"
# Warning: nginx-xxx: allowPrivilegeEscalation != false,
# unrestricted capabilities, runAsNonRoot != true, seccompProfileBaseline Profile Restrictions
| Control | Restricted | Requirement |
|---|---|---|
| HostProcess | Must not set | Pods cannot use Windows HostProcess |
| Host Namespaces | Must not set | No hostNetwork, hostPID, hostIPC |
| Privileged | Must not set | No privileged: true |
| Capabilities | Baseline list only | Only NET_BIND_SERVICE, drop ALL for restricted |
| HostPath Volumes | Must not use | No hostPath volume mounts |
| Host Ports | Must not use | No hostPort in container spec |
| AppArmor | Default/runtime | Cannot set to unconfined |
| SELinux | Limited types | Only container_t, container_init_t, container_kvm_t |
| /proc Mount Type | Default only | Must use Default proc mount |
| Seccomp | RuntimeDefault or Localhost | Must specify seccomp profile (restricted) |
| Sysctls | Safe set only | Limited to safe sysctls |
Validation Commands
# Verify namespace labels
kubectl get ns --show-labels | grep pod-security
# Test pod creation against policy
kubectl run test-pod --image=nginx --namespace=production --dry-run=server
# Check for violations in audit logs
kubectl get events --field-selector reason=FailedCreate -A
# Scan with Kubescape for PSS compliance
kubescape scan framework nsa --namespace productionReferences
Source materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md1.5 KB
API Reference: Implementing Kubernetes Pod Security Standards
PSA Namespace Labels
# Apply restricted enforcement
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted --overwritePod Security Standard Levels
| Level | Description | Blocks |
|---|---|---|
| Privileged | Unrestricted | Nothing |
| Baseline | Minimally restrictive | hostNetwork, privileged, hostPID/IPC |
| Restricted | Heavily restricted | + runAsNonRoot, drop ALL caps, seccomp |
PSA Modes
| Mode | Behavior |
|---|---|
| enforce | Reject violating pods |
| audit | Log violations (allow pod) |
| warn | Warn user (allow pod) |
Baseline Violations
| Field | Forbidden Value |
|---|---|
spec.hostNetwork |
true |
spec.hostPID |
true |
spec.hostIPC |
true |
containers[*].securityContext.privileged |
true |
containers[*].securityContext.capabilities.add |
Non-default |
Restricted Violations (adds to Baseline)
| Field | Required |
|---|---|
runAsNonRoot |
true |
allowPrivilegeEscalation |
false |
capabilities.drop |
["ALL"] |
seccompProfile.type |
RuntimeDefault or Localhost |
References
standards.md3.2 KB
Standards Reference - Kubernetes Pod Security Standards
Kubernetes Pod Security Standards (PSS) v1.31
Privileged Profile
- No restrictions applied
- Used for: kube-system, monitoring agents, CNI plugins, storage drivers
Baseline Profile Controls
| Control | Policy |
|---|---|
| HostProcess | Must be false |
| Host Namespaces | hostNetwork, hostPID, hostIPC must be false |
| Privileged Containers | Must be false |
| Capabilities | Cannot add beyond: AUDIT_WRITE, CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, MKNOD, NET_BIND_SERVICE, SETFCAP, SETGID, SETPCAP, SETUID, SYS_CHROOT |
| HostPath Volumes | Must not be used |
| Host Ports | Must not define hostPort |
| AppArmor | Must not set to unconfined |
| SELinux | type must be container_t, container_init_t, or container_kvm_t; user/role must not be set |
| /proc Mount Type | Must be Default |
| Seccomp | Must not set to Unconfined |
| Sysctls | Must only use safe sysctls |
Restricted Profile Controls (in addition to Baseline)
| Control | Policy |
|---|---|
| Volume Types | Only: configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected, secret |
| Privilege Escalation | allowPrivilegeEscalation must be false |
| Running as Non-root | runAsNonRoot must be true |
| Running as Non-root User | runAsUser must be non-zero |
| Seccomp | Must be RuntimeDefault or Localhost |
| Capabilities | Must drop ALL; may only add NET_BIND_SERVICE |
CIS Kubernetes Benchmark v1.8
Section 5: Policies
- 5.1: RBAC and Service Accounts
- 5.2: Pod Security Standards
- 5.2.1: Ensure PSA is not set to Privileged on non-system namespaces
- 5.2.2: Minimize admission of privileged containers
- 5.2.3: Minimize admission of containers wanting to share host process ID namespace
- 5.2.4: Minimize admission of containers wanting to share host IPC namespace
- 5.2.5: Minimize admission of containers wanting to share host network namespace
- 5.2.6: Minimize admission of containers with allowPrivilegeEscalation
- 5.2.7: Minimize admission of root containers
- 5.2.8: Minimize admission of containers with NET_RAW capability
- 5.2.9: Minimize admission of containers with added capabilities
- 5.2.10: Minimize admission of containers with capabilities assigned
- 5.2.11: Minimize admission of containers with HostProcess
- 5.2.12: Minimize admission of HostPath volumes
- 5.2.13: Minimize admission of containers with unrestricted Seccomp profile
NSA/CISA Kubernetes Hardening Guide
Pod Security Recommendations
- Use PSA in enforce mode for production namespaces
- Set restricted profile as default for all non-system namespaces
- Require seccomp profiles on all pods
- Prevent privileged containers in all workload namespaces
- Require non-root user for all containers
- Drop all capabilities and only add NET_BIND_SERVICE if needed
MITRE ATT&CK for Containers
Techniques Prevented by Restricted Profile
| Technique | PSS Control |
|---|---|
| T1611 - Escape to Host | Blocks privileged, hostPID, hostNetwork |
| T1610 - Deploy Container | Blocks privileged containers |
| T1053 - Scheduled Task | Blocks host namespace access |
| T1548 - Abuse Elevation Control | Blocks allowPrivilegeEscalation |
workflows.md3.6 KB
Workflows - Kubernetes Pod Security Standards
Workflow 1: PSS Migration from PodSecurityPolicy
[Identify PSP usage] --> [Map PSP to PSS levels] --> [Apply audit/warn labels]
| | |
v v v
kubectl get psp Privileged PSP -> baseline Monitor audit logs
List all namespaces Restrictive PSP -> restricted for 2-4 weeks
| | |
+------------------------+---------------------------+
|
v
[Enable enforce mode per namespace]
|
v
[Remove PodSecurityPolicy resources]
|
v
[Disable PSP admission controller]Workflow 2: New Namespace Onboarding
Step 1: Classify workload sensitivity
- System/Infrastructure -> Privileged (only kube-system)
- General workloads -> Baseline + Restricted warnings
- Production/Sensitive -> Restricted enforce
Step 2: Create namespace with labels
kubectl create namespace $NS
kubectl label namespace $NS \
pod-security.kubernetes.io/enforce=$LEVEL \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
Step 3: Test with dry-run
kubectl run test --image=nginx -n $NS --dry-run=server
Step 4: Deploy workloads with compliant security contexts
Step 5: Validate enforcement
kubectl get events -n $NS --field-selector reason=FailedCreateWorkflow 3: CI/CD PSS Compliance Check
# Pre-deployment validation
name: PSS Compliance Check
on: pull_request
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install kubescape
run: curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
- name: Scan manifests for PSS restricted compliance
run: |
kubescape scan framework nsa \
--controls-config controls.json \
--format junit --output results.xml \
k8s-manifests/
- name: Validate security contexts
run: |
for file in k8s-manifests/*.yaml; do
echo "Checking $file..."
# Verify runAsNonRoot
if ! grep -q "runAsNonRoot: true" "$file"; then
echo "FAIL: Missing runAsNonRoot in $file"
exit 1
fi
# Verify drop ALL
if ! grep -q "drop:" "$file" || ! grep -A1 "drop:" "$file" | grep -q "ALL"; then
echo "FAIL: Missing drop ALL capabilities in $file"
exit 1
fi
doneWorkflow 4: Violation Response
[PSA Violation Detected]
|
+-- enforce mode --> Pod rejected --> Alert developer
| |
| v
| Fix security context
| Re-deploy
|
+-- audit mode --> Pod allowed, audit log entry
| |
| v
| Weekly audit review
| Create remediation ticket
|
+-- warn mode --> Pod allowed, user warning
|
v
Developer sees warning
Fix before enforce rolloutScripts 2
agent.py7.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for auditing Kubernetes Pod Security Standards enforcement."""
import json
import argparse
import subprocess
from datetime import datetime
from collections import Counter
PSS_LEVELS = {
"privileged": {"order": 0, "description": "Unrestricted, for system workloads"},
"baseline": {"order": 1, "description": "Minimally restrictive, prevents known escalations"},
"restricted": {"order": 2, "description": "Heavily restricted, hardened best practices"},
}
BASELINE_VIOLATIONS = [
"hostNetwork", "hostPID", "hostIPC", "hostPorts",
"privileged", "allowPrivilegeEscalation",
"capabilities.add (non-default)", "seccomp (unconfined)",
]
RESTRICTED_VIOLATIONS = BASELINE_VIOLATIONS + [
"runAsNonRoot not set", "runAsUser=0",
"seccompProfile not RuntimeDefault/Localhost",
"capabilities.drop not ALL", "readOnlyRootFilesystem not set",
]
def kubectl_json(args_list):
"""Run kubectl and return JSON."""
cmd = ["kubectl"] + args_list + ["-o", "json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if result.returncode != 0:
return {"error": result.stderr.strip()}
return json.loads(result.stdout) if result.stdout.strip() else {}
def audit_namespace_labels():
"""Audit PSA labels on all namespaces."""
ns_data = kubectl_json(["get", "namespaces"])
if "error" in ns_data:
return ns_data
results = []
for ns in ns_data.get("items", []):
name = ns["metadata"]["name"]
labels = ns["metadata"].get("labels", {})
enforce = labels.get("pod-security.kubernetes.io/enforce", "")
audit = labels.get("pod-security.kubernetes.io/audit", "")
warn = labels.get("pod-security.kubernetes.io/warn", "")
system = name in ("kube-system", "kube-public", "kube-node-lease")
results.append({
"namespace": name, "system": system,
"enforce": enforce, "audit": audit, "warn": warn,
"protected": bool(enforce),
"severity": "INFO" if enforce or system else "HIGH",
})
return results
def audit_pod_security(pods_path):
"""Audit pods against Pod Security Standards."""
with open(pods_path) as f:
data = json.load(f)
pods = data if isinstance(data, list) else data.get("items", [])
findings = []
for pod in pods:
metadata = pod.get("metadata", {})
spec = pod.get("spec", {})
pod_name = metadata.get("name", "")
ns = metadata.get("namespace", "default")
if spec.get("hostNetwork"):
findings.append({"pod": pod_name, "ns": ns, "violation": "hostNetwork",
"level": "baseline", "severity": "HIGH"})
if spec.get("hostPID"):
findings.append({"pod": pod_name, "ns": ns, "violation": "hostPID",
"level": "baseline", "severity": "HIGH"})
for container in spec.get("containers", []) + spec.get("initContainers", []):
sc = container.get("securityContext", {})
c_name = container.get("name", "")
if sc.get("privileged"):
findings.append({"pod": pod_name, "container": c_name, "ns": ns,
"violation": "privileged", "level": "baseline",
"severity": "CRITICAL"})
if sc.get("allowPrivilegeEscalation", True):
findings.append({"pod": pod_name, "container": c_name, "ns": ns,
"violation": "allowPrivilegeEscalation",
"level": "restricted", "severity": "MEDIUM"})
if not sc.get("runAsNonRoot"):
findings.append({"pod": pod_name, "container": c_name, "ns": ns,
"violation": "runAsNonRoot not set",
"level": "restricted", "severity": "MEDIUM"})
caps = sc.get("capabilities", {})
added = caps.get("add", [])
if added and any(c not in ("NET_BIND_SERVICE",) for c in added):
findings.append({"pod": pod_name, "container": c_name, "ns": ns,
"violation": f"capabilities.add: {added}",
"level": "baseline", "severity": "HIGH"})
dropped = caps.get("drop", [])
if "ALL" not in dropped:
findings.append({"pod": pod_name, "container": c_name, "ns": ns,
"violation": "capabilities.drop not ALL",
"level": "restricted", "severity": "MEDIUM"})
return findings
def generate_namespace_labels(namespace, level="restricted"):
"""Generate PSA label patch for a namespace."""
labels = {
f"pod-security.kubernetes.io/enforce": level,
f"pod-security.kubernetes.io/audit": level,
f"pod-security.kubernetes.io/warn": level,
}
return {
"command": f'kubectl label namespace {namespace} '
f'pod-security.kubernetes.io/enforce={level} '
f'pod-security.kubernetes.io/audit={level} '
f'pod-security.kubernetes.io/warn={level} --overwrite',
"labels": labels,
}
def generate_compliance_report(findings):
"""Generate PSS compliance summary."""
by_level = Counter(f.get("level", "unknown") for f in findings)
by_severity = Counter(f.get("severity", "unknown") for f in findings)
by_violation = Counter(f.get("violation", "unknown") for f in findings)
return {
"total_violations": len(findings),
"by_level": dict(by_level),
"by_severity": dict(by_severity),
"top_violations": dict(by_violation.most_common(10)),
"baseline_violations": by_level.get("baseline", 0),
"restricted_violations": by_level.get("restricted", 0),
}
def main():
parser = argparse.ArgumentParser(description="Kubernetes Pod Security Standards Agent")
parser.add_argument("--pods", help="Pods JSON to audit")
parser.add_argument("--namespace", help="Namespace for label generation")
parser.add_argument("--level", choices=["privileged", "baseline", "restricted"],
default="restricted")
parser.add_argument("--action", choices=["audit-ns", "audit-pods", "generate", "full"],
default="full")
parser.add_argument("--output", default="pss_report.json")
args = parser.parse_args()
report = {"generated_at": datetime.utcnow().isoformat(), "results": {}}
if args.action in ("audit-ns", "full"):
ns_audit = audit_namespace_labels()
report["results"]["namespace_audit"] = ns_audit
if isinstance(ns_audit, list):
unprotected = sum(1 for n in ns_audit if not n["protected"] and not n["system"])
print(f"[+] Namespaces: {unprotected} unprotected")
if args.action in ("audit-pods", "full") and args.pods:
findings = audit_pod_security(args.pods)
summary = generate_compliance_report(findings)
report["results"]["pod_audit"] = findings
report["results"]["summary"] = summary
print(f"[+] Pod violations: {summary['total_violations']}")
if args.action in ("generate", "full") and args.namespace:
labels = generate_namespace_labels(args.namespace, args.level)
report["results"]["generated"] = labels
print(f"[+] Labels for {args.namespace}: {args.level}")
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()
process.py11.1 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Kubernetes Pod Security Standards Compliance Checker
Audits namespaces and workloads for PSS enforcement levels
and identifies non-compliant pods.
"""
import subprocess
import json
import sys
from dataclasses import dataclass, field
@dataclass
class PSSFinding:
namespace: str
resource: str
level: str # baseline, restricted
violation: str
severity: str # CRITICAL, HIGH, MEDIUM
remediation: str
@dataclass
class PSSReport:
findings: list = field(default_factory=list)
namespaces_audited: int = 0
pods_audited: int = 0
compliant_pods: int = 0
non_compliant_pods: int = 0
def run_kubectl(args: list) -> tuple:
"""Execute kubectl command and return parsed JSON."""
cmd = ["kubectl"] + args + ["-o", "json"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
return None, result.stderr
return json.loads(result.stdout), None
except (subprocess.TimeoutExpired, json.JSONDecodeError) as e:
return None, str(e)
def get_namespace_pss_labels(ns_data: dict) -> dict:
"""Extract PSS labels from namespace metadata."""
labels = ns_data.get("metadata", {}).get("labels", {})
return {
"enforce": labels.get("pod-security.kubernetes.io/enforce", "not-set"),
"audit": labels.get("pod-security.kubernetes.io/audit", "not-set"),
"warn": labels.get("pod-security.kubernetes.io/warn", "not-set"),
"enforce-version": labels.get("pod-security.kubernetes.io/enforce-version", "not-set"),
}
def check_restricted_compliance(pod_spec: dict, pod_name: str, namespace: str) -> list:
"""Check pod spec against restricted PSS profile."""
findings = []
containers = pod_spec.get("containers", []) + pod_spec.get("initContainers", [])
pod_security_context = pod_spec.get("securityContext", {})
# Check pod-level runAsNonRoot
pod_run_as_non_root = pod_security_context.get("runAsNonRoot", False)
pod_run_as_user = pod_security_context.get("runAsUser")
# Check pod-level seccomp
pod_seccomp = pod_security_context.get("seccompProfile", {})
pod_seccomp_type = pod_seccomp.get("type", "")
# Check hostNetwork, hostPID, hostIPC
for host_ns in ["hostNetwork", "hostPID", "hostIPC"]:
if pod_spec.get(host_ns, False):
findings.append(PSSFinding(
namespace=namespace,
resource=pod_name,
level="baseline",
violation=f"{host_ns} is enabled",
severity="CRITICAL",
remediation=f"Set {host_ns}: false in pod spec"
))
# Check hostPath volumes
for vol in pod_spec.get("volumes", []):
if "hostPath" in vol:
findings.append(PSSFinding(
namespace=namespace,
resource=pod_name,
level="baseline",
violation=f"hostPath volume '{vol.get('name')}' detected",
severity="HIGH",
remediation="Replace hostPath with emptyDir, PVC, or configMap"
))
for container in containers:
c_name = container.get("name", "unknown")
sc = container.get("securityContext", {})
# Check privileged
if sc.get("privileged", False):
findings.append(PSSFinding(
namespace=namespace,
resource=f"{pod_name}/{c_name}",
level="baseline",
violation="Container runs in privileged mode",
severity="CRITICAL",
remediation="Set privileged: false and use specific capabilities"
))
# Check allowPrivilegeEscalation
if sc.get("allowPrivilegeEscalation", True):
findings.append(PSSFinding(
namespace=namespace,
resource=f"{pod_name}/{c_name}",
level="restricted",
violation="allowPrivilegeEscalation is not explicitly false",
severity="HIGH",
remediation="Set allowPrivilegeEscalation: false"
))
# Check runAsNonRoot
c_run_as_non_root = sc.get("runAsNonRoot")
c_run_as_user = sc.get("runAsUser")
if not (c_run_as_non_root or pod_run_as_non_root):
if not (c_run_as_user and c_run_as_user > 0) and not (pod_run_as_user and pod_run_as_user > 0):
findings.append(PSSFinding(
namespace=namespace,
resource=f"{pod_name}/{c_name}",
level="restricted",
violation="Container may run as root (runAsNonRoot not set)",
severity="HIGH",
remediation="Set runAsNonRoot: true and runAsUser to non-zero value"
))
# Check capabilities
caps = sc.get("capabilities", {})
drop_caps = [c.upper() for c in (caps.get("drop") or [])]
add_caps = [c.upper() for c in (caps.get("add") or [])]
if "ALL" not in drop_caps:
findings.append(PSSFinding(
namespace=namespace,
resource=f"{pod_name}/{c_name}",
level="restricted",
violation="Capabilities not dropped (missing drop: ALL)",
severity="HIGH",
remediation="Add capabilities: drop: ['ALL'] to security context"
))
allowed_add = {"NET_BIND_SERVICE"}
extra_caps = set(add_caps) - allowed_add
if extra_caps:
findings.append(PSSFinding(
namespace=namespace,
resource=f"{pod_name}/{c_name}",
level="restricted",
violation=f"Disallowed capabilities added: {extra_caps}",
severity="HIGH",
remediation="Only NET_BIND_SERVICE may be added in restricted profile"
))
# Check seccomp
c_seccomp = sc.get("seccompProfile", {})
c_seccomp_type = c_seccomp.get("type", pod_seccomp_type)
if c_seccomp_type not in ("RuntimeDefault", "Localhost"):
findings.append(PSSFinding(
namespace=namespace,
resource=f"{pod_name}/{c_name}",
level="restricted",
violation=f"Seccomp profile not set or is '{c_seccomp_type}'",
severity="MEDIUM",
remediation="Set seccompProfile.type to RuntimeDefault or Localhost"
))
# Check readOnlyRootFilesystem (recommended, not required by PSS)
if not sc.get("readOnlyRootFilesystem", False):
findings.append(PSSFinding(
namespace=namespace,
resource=f"{pod_name}/{c_name}",
level="restricted",
violation="Root filesystem is not read-only (recommended)",
severity="MEDIUM",
remediation="Set readOnlyRootFilesystem: true and use emptyDir for writable paths"
))
return findings
def audit_cluster(report: PSSReport):
"""Audit all namespaces and pods for PSS compliance."""
# Get all namespaces
ns_data, err = run_kubectl(["get", "namespaces"])
if ns_data is None:
print(f"[!] Failed to get namespaces: {err}")
return
namespaces = ns_data.get("items", [])
print(f"[*] Found {len(namespaces)} namespaces")
for ns in namespaces:
ns_name = ns["metadata"]["name"]
pss_labels = get_namespace_pss_labels(ns)
report.namespaces_audited += 1
enforce_level = pss_labels["enforce"]
print(f"\n[*] Namespace: {ns_name} (enforce={enforce_level})")
# Flag namespaces without PSS enforcement
if enforce_level == "not-set":
report.findings.append(PSSFinding(
namespace=ns_name,
resource="namespace",
level="baseline",
violation="No PSS enforcement label set",
severity="HIGH" if ns_name not in ("kube-system", "kube-public", "kube-node-lease") else "MEDIUM",
remediation=f"kubectl label namespace {ns_name} pod-security.kubernetes.io/enforce=baseline"
))
# Get pods in namespace
pods_data, err = run_kubectl(["get", "pods", "-n", ns_name])
if pods_data is None:
continue
pods = pods_data.get("items", [])
for pod in pods:
pod_name = pod["metadata"]["name"]
pod_spec = pod.get("spec", {})
report.pods_audited += 1
pod_findings = check_restricted_compliance(pod_spec, pod_name, ns_name)
if pod_findings:
report.non_compliant_pods += 1
report.findings.extend(pod_findings)
else:
report.compliant_pods += 1
def print_report(report: PSSReport):
"""Print audit results."""
print("\n" + "=" * 70)
print("KUBERNETES POD SECURITY STANDARDS AUDIT REPORT")
print("=" * 70)
print(f"Namespaces audited: {report.namespaces_audited}")
print(f"Pods audited: {report.pods_audited}")
print(f"Compliant pods: {report.compliant_pods}")
print(f"Non-compliant pods: {report.non_compliant_pods}")
print(f"Total findings: {len(report.findings)}")
if report.pods_audited > 0:
compliance_rate = (report.compliant_pods / report.pods_audited) * 100
print(f"Compliance rate: {compliance_rate:.1f}%")
print("=" * 70)
# Group by severity
for severity in ["CRITICAL", "HIGH", "MEDIUM"]:
severity_findings = [f for f in report.findings if f.severity == severity]
if severity_findings:
print(f"\n{severity} FINDINGS ({len(severity_findings)}):")
print("-" * 70)
for f in severity_findings:
print(f" [{f.namespace}] {f.resource}")
print(f" Level: {f.level} | Violation: {f.violation}")
print(f" Fix: {f.remediation}")
print()
def main():
print("[*] Kubernetes Pod Security Standards Compliance Checker")
print("[*] Checking against restricted profile\n")
report = PSSReport()
audit_cluster(report)
print_report(report)
# Save JSON report
output = {
"summary": {
"namespaces": report.namespaces_audited,
"pods_audited": report.pods_audited,
"compliant": report.compliant_pods,
"non_compliant": report.non_compliant_pods,
},
"findings": [
{
"namespace": f.namespace,
"resource": f.resource,
"level": f.level,
"violation": f.violation,
"severity": f.severity,
"remediation": f.remediation,
}
for f in report.findings
],
}
with open("pss_audit_report.json", "w") as f:
json.dump(output, f, indent=2)
print("[*] Report saved to pss_audit_report.json")
critical_high = [f for f in report.findings if f.severity in ("CRITICAL", "HIGH")]
if critical_high:
print(f"\n[!] {len(critical_high)} CRITICAL/HIGH findings require attention")
sys.exit(1)
if __name__ == "__main__":
main()
Assets 1
template.mdtext/markdown · 1.6 KBKeep exploring