Expand 39 api-reference stubs with real tool docs, expand 15 agent.py boilerplate stubs

This commit is contained in:
mukul975
2026-03-19 13:29:50 +01:00
parent d63b578a2f
commit d005ae764b
191 changed files with 4300 additions and 587 deletions
@@ -1,61 +1,253 @@
#!/usr/bin/env python3
"""Kubernetes RBAC hardening audit."""
import argparse, json
"""Kubernetes RBAC hardening audit agent.
Audits Kubernetes Role-Based Access Control configuration for security
weaknesses including overly permissive ClusterRoles, wildcard permissions,
privilege escalation paths, and service account misconfigurations.
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
try:
import requests
except ImportError:
requests = None
def audit_config(target, token):
findings = []
if not requests: return [{"error": "requests required"}]
headers = {"Authorization": f"Bearer {token}"}
def run_kubectl(args_list, timeout=60):
"""Execute kubectl and return parsed JSON."""
cmd = ["kubectl"] + args_list + ["-o", "json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
if result.returncode != 0:
return None
try:
resp = requests.get(f"{target}/api/v1/status", headers=headers, timeout=10)
if resp.status_code == 200:
data = resp.json()
if not data.get("enabled", True):
findings.append({"check": "Service Status", "status": "DISABLED", "severity": "CRITICAL"})
elif resp.status_code == 401:
findings.append({"check": "Authentication", "status": "UNAUTHORIZED", "severity": "HIGH"})
except requests.RequestException as e:
findings.append({"error": str(e)})
return json.loads(result.stdout)
except json.JSONDecodeError:
return None
def audit_cluster_roles():
"""Audit ClusterRoles for dangerous permissions."""
findings = []
data = run_kubectl(["get", "clusterroles"])
if not data:
return findings
dangerous_verbs = {"*", "create", "update", "patch", "delete"}
dangerous_resources = {"secrets", "pods/exec", "clusterroles", "clusterrolebindings",
"roles", "rolebindings", "serviceaccounts", "nodes"}
escalation_resources = {"clusterroles", "clusterrolebindings", "roles", "rolebindings"}
for role in data.get("items", []):
name = role.get("metadata", {}).get("name", "")
if name.startswith("system:"):
continue # Skip system roles
for rule in role.get("rules", []):
verbs = set(rule.get("verbs", []))
resources = set(rule.get("resources", []))
api_groups = rule.get("apiGroups", [])
# Wildcard everything
if "*" in verbs and "*" in resources:
findings.append({
"type": "wildcard_all", "role": name, "kind": "ClusterRole",
"severity": "CRITICAL",
"detail": "Full wildcard access (verbs: *, resources: *)",
"recommendation": "Replace with specific verbs and resources",
})
# Secrets access
if resources & {"secrets", "*"} and verbs & {"get", "list", "watch", "*"}:
findings.append({
"type": "secrets_access", "role": name, "kind": "ClusterRole",
"severity": "HIGH",
"detail": f"Can read secrets (verbs: {verbs & {'get', 'list', 'watch', '*'}})",
})
# Pod exec
if "pods/exec" in resources or ("pods" in resources and "create" in verbs):
findings.append({
"type": "pod_exec", "role": name, "kind": "ClusterRole",
"severity": "HIGH",
"detail": "Can exec into pods (container escape risk)",
})
# Privilege escalation via RBAC modification
if resources & escalation_resources and verbs & {"create", "update", "patch", "*"}:
findings.append({
"type": "rbac_escalation", "role": name, "kind": "ClusterRole",
"severity": "CRITICAL",
"detail": f"Can modify RBAC resources: {resources & escalation_resources}",
})
# Node access
if "nodes" in resources and verbs & {"get", "list", "proxy", "*"}:
findings.append({
"type": "node_access", "role": name, "kind": "ClusterRole",
"severity": "MEDIUM",
"detail": "Can access node resources",
})
return findings
def check_compliance(target, token):
def audit_cluster_role_bindings():
"""Audit ClusterRoleBindings for overly broad subject assignments."""
findings = []
if not requests: return []
headers = {"Authorization": f"Bearer {token}"}
try:
resp = requests.get(f"{target}/api/v1/compliance", headers=headers, timeout=10)
if resp.status_code == 200:
for item in resp.json().get("checks", []):
if item.get("status") != "PASS":
findings.append({"check": item.get("name"), "status": item.get("status"),
"severity": item.get("severity", "MEDIUM")})
except requests.RequestException:
pass
data = run_kubectl(["get", "clusterrolebindings"])
if not data:
return findings
for binding in data.get("items", []):
name = binding.get("metadata", {}).get("name", "")
if name.startswith("system:"):
continue
role_ref = binding.get("roleRef", {})
role_name = role_ref.get("name", "")
subjects = binding.get("subjects", [])
for subject in subjects:
kind = subject.get("kind", "")
subj_name = subject.get("name", "")
namespace = subject.get("namespace", "")
# Cluster-admin binding
if role_name == "cluster-admin":
findings.append({
"type": "cluster_admin_binding",
"binding": name, "subject": f"{kind}/{subj_name}",
"severity": "CRITICAL",
"detail": f"cluster-admin bound to {kind} '{subj_name}'",
})
# Group bindings to all authenticated/unauthenticated
if kind == "Group" and subj_name in ("system:authenticated", "system:unauthenticated"):
findings.append({
"type": "broad_group_binding",
"binding": name, "subject": subj_name,
"severity": "CRITICAL" if subj_name == "system:unauthenticated" else "HIGH",
"detail": f"Role '{role_name}' bound to group '{subj_name}'",
})
# Default service account bindings
if kind == "ServiceAccount" and subj_name == "default":
findings.append({
"type": "default_sa_binding",
"binding": name, "subject": f"default SA in {namespace}",
"severity": "MEDIUM",
"detail": f"Role '{role_name}' bound to default service account",
})
return findings
def audit_service_accounts(namespace=None):
"""Audit service accounts for misconfigurations."""
findings = []
cmd = ["get", "serviceaccounts", "--all-namespaces"] if not namespace else ["get", "serviceaccounts", "-n", namespace]
data = run_kubectl(cmd)
if not data:
return findings
for sa in data.get("items", []):
name = sa.get("metadata", {}).get("name", "")
ns = sa.get("metadata", {}).get("namespace", "")
automount = sa.get("automountServiceAccountToken", None)
if name == "default" and automount is not False:
findings.append({
"type": "default_sa_automount",
"namespace": ns, "service_account": name,
"severity": "MEDIUM",
"detail": f"Default SA in '{ns}' has automountServiceAccountToken enabled",
"recommendation": "Set automountServiceAccountToken: false on default SA",
})
secrets = sa.get("secrets", [])
if len(secrets) > 1:
findings.append({
"type": "sa_multiple_secrets",
"namespace": ns, "service_account": name,
"severity": "LOW",
"detail": f"SA has {len(secrets)} token secrets",
})
return findings
def format_summary(role_findings, binding_findings, sa_findings):
"""Print RBAC audit summary."""
all_findings = role_findings + binding_findings + sa_findings
print(f"\n{'='*60}")
print(f" Kubernetes RBAC Hardening Audit")
print(f"{'='*60}")
print(f" ClusterRole Issues : {len(role_findings)}")
print(f" Binding Issues : {len(binding_findings)}")
print(f" ServiceAccount Issues : {len(sa_findings)}")
print(f" Total Findings : {len(all_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"]:
count = severity_counts.get(sev, 0)
if count:
print(f" {sev:10s}: {count}")
if all_findings:
print(f"\n Top Findings:")
for f in sorted(all_findings, key=lambda x: {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2}.get(x["severity"], 9))[:15]:
print(f" [{f['severity']:8s}] {f['type']:25s} | {f.get('detail', '')[:50]}")
return severity_counts
def main():
p = argparse.ArgumentParser(description="Kubernetes RBAC hardening audit")
p.add_argument("--target", required=True, help="Target URL")
p.add_argument("--token", required=True, help="API token")
p.add_argument("--output", "-o", help="Output JSON report")
p.add_argument("--verbose", "-v", action="store_true")
a = p.parse_args()
print("[*] Kubernetes RBAC hardening audit")
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}
report["findings"].extend(audit_config(a.target, a.token))
report["findings"].extend(check_compliance(a.target, a.token))
high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL"))
report["risk_level"] = "HIGH" if high else "MEDIUM" if report["findings"] else "LOW"
print(f"[*] {len(report['findings'])} findings, risk: {report['risk_level']}")
if a.output:
with open(a.output, "w") as f: json.dump(report, f, indent=2)
else:
parser = argparse.ArgumentParser(description="Kubernetes RBAC hardening audit agent")
parser.add_argument("--namespace", "-n", help="Specific namespace to audit")
parser.add_argument("--kubeconfig", help="Path to kubeconfig")
parser.add_argument("--skip-roles", action="store_true")
parser.add_argument("--skip-bindings", action="store_true")
parser.add_argument("--skip-sa", action="store_true")
parser.add_argument("--output", "-o", help="Output JSON report")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
if args.kubeconfig:
os.environ["KUBECONFIG"] = args.kubeconfig
role_findings = [] if args.skip_roles else audit_cluster_roles()
binding_findings = [] if args.skip_bindings else audit_cluster_role_bindings()
sa_findings = [] if args.skip_sa else audit_service_accounts(args.namespace)
severity_counts = format_summary(role_findings, binding_findings, sa_findings)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "K8s RBAC Audit",
"role_findings": role_findings,
"binding_findings": binding_findings,
"sa_findings": sa_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()