mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-19 22:19:39 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
---
|
||||
name: None
|
||||
description: 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+. PS
|
||||
domain: cybersecurity
|
||||
subdomain: container-security
|
||||
tags: [containers, kubernetes, security, pod-security, PSA]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
# Implementing Kubernetes Pod Security Standards
|
||||
|
||||
## 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.
|
||||
|
||||
## 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 |
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Label Namespaces for PSA
|
||||
|
||||
```yaml
|
||||
# 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
|
||||
```
|
||||
|
||||
```yaml
|
||||
# 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
|
||||
```
|
||||
|
||||
```yaml
|
||||
# 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: latest
|
||||
```
|
||||
|
||||
### Step 2: Apply Labels to Existing Namespaces
|
||||
|
||||
```bash
|
||||
# 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/enforce
|
||||
```
|
||||
|
||||
### Step 3: Create Compliant Pod Specs
|
||||
|
||||
```yaml
|
||||
# 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: 50Mi
|
||||
```
|
||||
|
||||
### Step 4: Gradual Migration Strategy
|
||||
|
||||
```bash
|
||||
# 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 \
|
||||
--overwrite
|
||||
```
|
||||
|
||||
### Step 5: Dry-Run Enforcement Testing
|
||||
|
||||
```bash
|
||||
# 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, seccompProfile
|
||||
```
|
||||
|
||||
## Baseline 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
|
||||
|
||||
```bash
|
||||
# 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 production
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Pod Security Standards - Kubernetes](https://kubernetes.io/docs/concepts/security/pod-security-standards/)
|
||||
- [Pod Security Admission - Kubernetes](https://kubernetes.io/docs/concepts/security/pod-security-admission/)
|
||||
- [Migrate from PodSecurityPolicy](https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/)
|
||||
- [Kubescape PSS Scanner](https://github.com/kubescape/kubescape)
|
||||
@@ -0,0 +1,55 @@
|
||||
# Pod Security Standards Implementation Template
|
||||
|
||||
## Namespace Classification
|
||||
|
||||
| Namespace | Current PSS Level | Target PSS Level | Migration Status |
|
||||
|-----------|------------------|------------------|------------------|
|
||||
| kube-system | privileged | privileged | N/A |
|
||||
| production | | restricted | |
|
||||
| staging | | baseline | |
|
||||
| development | | baseline | |
|
||||
|
||||
## PSS Label Configuration
|
||||
|
||||
| Namespace | enforce | audit | warn | Version |
|
||||
|-----------|---------|-------|------|---------|
|
||||
| | | | | latest |
|
||||
|
||||
## Workload Compliance Checklist
|
||||
|
||||
### Pod Security Context
|
||||
- [ ] runAsNonRoot: true
|
||||
- [ ] runAsUser: non-zero (e.g., 65534)
|
||||
- [ ] runAsGroup: non-zero
|
||||
- [ ] fsGroup: appropriate group ID
|
||||
- [ ] seccompProfile.type: RuntimeDefault
|
||||
|
||||
### Container Security Context
|
||||
- [ ] allowPrivilegeEscalation: false
|
||||
- [ ] readOnlyRootFilesystem: true
|
||||
- [ ] capabilities.drop: ["ALL"]
|
||||
- [ ] capabilities.add: only NET_BIND_SERVICE if needed
|
||||
- [ ] privileged: false (or not set)
|
||||
|
||||
### Pod Spec
|
||||
- [ ] automountServiceAccountToken: false (unless needed)
|
||||
- [ ] No hostNetwork, hostPID, hostIPC
|
||||
- [ ] No hostPath volumes
|
||||
- [ ] No hostPort in container specs
|
||||
- [ ] Resource requests and limits defined
|
||||
|
||||
## Migration Plan
|
||||
|
||||
| Phase | Action | Timeline | Status |
|
||||
|-------|--------|----------|--------|
|
||||
| 1 | Apply audit+warn labels | Week 1 | |
|
||||
| 2 | Review audit violations | Week 2-3 | |
|
||||
| 3 | Fix workload security contexts | Week 4-6 | |
|
||||
| 4 | Enable baseline enforce | Week 7 | |
|
||||
| 5 | Enable restricted enforce | Week 8 | |
|
||||
|
||||
## Exceptions
|
||||
|
||||
| Namespace | Workload | Required Level | Justification | Approved By |
|
||||
|-----------|----------|---------------|---------------|-------------|
|
||||
| | | | | |
|
||||
@@ -0,0 +1,71 @@
|
||||
# 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 |
|
||||
@@ -0,0 +1,110 @@
|
||||
# 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=FailedCreate
|
||||
```
|
||||
|
||||
## Workflow 3: CI/CD PSS Compliance Check
|
||||
|
||||
```yaml
|
||||
# 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
|
||||
done
|
||||
```
|
||||
|
||||
## Workflow 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 rollout
|
||||
```
|
||||
@@ -0,0 +1,308 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user