mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
196 lines
7.4 KiB
Python
196 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Kubernetes etcd Security Assessment Tool
|
|
|
|
Checks etcd security configuration including TLS, encryption at rest,
|
|
access controls, certificate expiration, and backup status.
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import argparse
|
|
import ssl
|
|
import socket
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
|
|
def run_command(cmd: list[str], timeout: int = 15) -> tuple[str, str, int]:
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
|
return result.stdout.strip(), result.stderr.strip(), result.returncode
|
|
except (subprocess.TimeoutExpired, FileNotFoundError) as e:
|
|
return "", str(e), 1
|
|
|
|
|
|
def check_etcd_tls() -> list[dict]:
|
|
"""Check etcd TLS configuration via API server process."""
|
|
findings = []
|
|
stdout, _, _ = run_command(["kubectl", "get", "pod", "etcd-*", "-n", "kube-system",
|
|
"-o", "jsonpath={.items[0].spec.containers[0].command}"])
|
|
if not stdout:
|
|
stdout, _, _ = run_command(["kubectl", "get", "pods", "-n", "kube-system",
|
|
"-l", "component=etcd", "-o", "json"])
|
|
if not stdout:
|
|
findings.append({"severity": "WARNING", "type": "etcd_access",
|
|
"description": "Cannot access etcd pod configuration"})
|
|
return findings
|
|
|
|
required_flags = {
|
|
"--cert-file": "Client TLS certificate",
|
|
"--key-file": "Client TLS key",
|
|
"--trusted-ca-file": "Client CA certificate",
|
|
"--peer-cert-file": "Peer TLS certificate",
|
|
"--peer-key-file": "Peer TLS key",
|
|
"--peer-trusted-ca-file": "Peer CA certificate",
|
|
"--client-cert-auth": "Client certificate authentication",
|
|
"--peer-client-cert-auth": "Peer certificate authentication",
|
|
}
|
|
|
|
for flag, desc in required_flags.items():
|
|
if flag not in stdout:
|
|
findings.append({
|
|
"severity": "CRITICAL" if "cert" in flag else "HIGH",
|
|
"type": "missing_tls_flag",
|
|
"flag": flag,
|
|
"description": f"etcd missing {desc} ({flag})"
|
|
})
|
|
|
|
dangerous_flags = {"--auto-tls=true": "Auto TLS enabled (insecure)",
|
|
"--peer-auto-tls=true": "Peer auto TLS enabled (insecure)"}
|
|
for flag, desc in dangerous_flags.items():
|
|
if flag in stdout:
|
|
findings.append({
|
|
"severity": "CRITICAL", "type": "insecure_tls",
|
|
"flag": flag, "description": desc
|
|
})
|
|
|
|
return findings
|
|
|
|
|
|
def check_encryption_at_rest() -> list[dict]:
|
|
"""Check if encryption at rest is configured for secrets."""
|
|
findings = []
|
|
stdout, _, _ = run_command(["kubectl", "get", "pods", "-n", "kube-system",
|
|
"-l", "component=kube-apiserver", "-o", "json"])
|
|
if not stdout:
|
|
findings.append({"severity": "WARNING", "type": "api_access",
|
|
"description": "Cannot access API server pod"})
|
|
return findings
|
|
|
|
if "--encryption-provider-config" not in stdout:
|
|
findings.append({
|
|
"severity": "CRITICAL", "type": "no_encryption",
|
|
"description": "Secrets encryption at rest is NOT configured (--encryption-provider-config missing)"
|
|
})
|
|
else:
|
|
findings.append({
|
|
"severity": "INFO", "type": "encryption_configured",
|
|
"description": "Encryption at rest configuration flag is present"
|
|
})
|
|
|
|
return findings
|
|
|
|
|
|
def check_etcd_network_exposure() -> list[dict]:
|
|
"""Check if etcd is exposed on non-localhost interfaces."""
|
|
findings = []
|
|
stdout, _, _ = run_command(["kubectl", "get", "pods", "-n", "kube-system",
|
|
"-l", "component=etcd", "-o", "json"])
|
|
if not stdout:
|
|
return findings
|
|
|
|
if "0.0.0.0:2379" in stdout:
|
|
findings.append({
|
|
"severity": "CRITICAL", "type": "etcd_exposed",
|
|
"description": "etcd client port listening on all interfaces (0.0.0.0:2379)"
|
|
})
|
|
if "0.0.0.0:2380" in stdout:
|
|
findings.append({
|
|
"severity": "HIGH", "type": "etcd_peer_exposed",
|
|
"description": "etcd peer port listening on all interfaces (0.0.0.0:2380)"
|
|
})
|
|
|
|
return findings
|
|
|
|
|
|
def check_etcd_pod_security() -> list[dict]:
|
|
"""Check etcd pod security context."""
|
|
findings = []
|
|
stdout, _, _ = run_command(["kubectl", "get", "pod", "-n", "kube-system",
|
|
"-l", "component=etcd", "-o", "json"])
|
|
if not stdout:
|
|
return findings
|
|
|
|
try:
|
|
data = json.loads(stdout)
|
|
for pod in data.get("items", []):
|
|
for container in pod["spec"].get("containers", []):
|
|
sc = container.get("securityContext", {})
|
|
if not sc.get("readOnlyRootFilesystem", False):
|
|
findings.append({
|
|
"severity": "LOW", "type": "etcd_writable_fs",
|
|
"description": "etcd container does not have readOnlyRootFilesystem"
|
|
})
|
|
host_mounts = [v for v in container.get("volumeMounts", [])
|
|
if "/etc/kubernetes/pki" in v.get("mountPath", "")]
|
|
if host_mounts:
|
|
findings.append({
|
|
"severity": "INFO", "type": "etcd_pki_mount",
|
|
"description": f"etcd mounts PKI directory: {host_mounts[0]['mountPath']}"
|
|
})
|
|
except (json.JSONDecodeError, KeyError):
|
|
pass
|
|
|
|
return findings
|
|
|
|
|
|
def generate_report(all_findings: list[dict], output_format: str = "text") -> str:
|
|
critical = [f for f in all_findings if f["severity"] == "CRITICAL"]
|
|
high = [f for f in all_findings if f["severity"] == "HIGH"]
|
|
medium = [f for f in all_findings if f["severity"] in ("MEDIUM", "WARNING")]
|
|
info = [f for f in all_findings if f["severity"] in ("LOW", "INFO")]
|
|
|
|
if output_format == "json":
|
|
return json.dumps({
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"summary": {"critical": len(critical), "high": len(high),
|
|
"medium": len(medium), "info": len(info)},
|
|
"findings": all_findings
|
|
}, indent=2)
|
|
|
|
lines = ["=" * 70, "KUBERNETES ETCD SECURITY ASSESSMENT REPORT",
|
|
f"Generated: {datetime.utcnow().isoformat()}", "=" * 70]
|
|
lines.append(f"\nFindings: {len(critical)} Critical, {len(high)} High, {len(medium)} Medium, {len(info)} Info")
|
|
|
|
for sev, items in [("CRITICAL", critical), ("HIGH", high), ("MEDIUM/WARNING", medium), ("INFO", info)]:
|
|
if items:
|
|
lines.append(f"\n## {sev}")
|
|
for f in items:
|
|
lines.append(f" [{f['type']}] {f['description']}")
|
|
|
|
passed = len(critical) == 0 and len(high) == 0
|
|
lines.append(f"\nAssessment Result: {'PASS' if passed else 'FAIL'}")
|
|
lines.append("=" * 70)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="etcd Security Assessment Tool")
|
|
parser.add_argument("--format", choices=["text", "json"], default="text")
|
|
args = parser.parse_args()
|
|
|
|
all_findings = []
|
|
all_findings.extend(check_etcd_tls())
|
|
all_findings.extend(check_encryption_at_rest())
|
|
all_findings.extend(check_etcd_network_exposure())
|
|
all_findings.extend(check_etcd_pod_security())
|
|
|
|
print(generate_report(all_findings, args.format))
|
|
sys.exit(1 if any(f["severity"] == "CRITICAL" for f in all_findings) else 0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|