#!/usr/bin/env python3
"""GCP penetration testing agent: bucket enumeration, IAM audit, privilege escalation analysis.
# For authorized testing only
"""

import json
import subprocess
import argparse
from datetime import datetime

BUCKET_PERMUTATIONS = [
    "{keyword}", "{keyword}-dev", "{keyword}-staging", "{keyword}-prod",
    "{keyword}-backup", "{keyword}-data", "{keyword}-logs", "{keyword}-assets",
    "{keyword}-uploads", "{keyword}-internal", "{keyword}-private", "{keyword}-public",
    "dev-{keyword}", "staging-{keyword}", "prod-{keyword}", "backup-{keyword}",
]

PRIV_ESC_PERMISSIONS = [
    "iam.serviceAccounts.actAs",
    "iam.serviceAccounts.getAccessToken",
    "iam.serviceAccounts.implicitDelegation",
    "iam.serviceAccounts.signBlob",
    "iam.serviceAccounts.signJwt",
    "iam.serviceAccountKeys.create",
    "resourcemanager.projects.setIamPolicy",
    "deploymentmanager.deployments.create",
    "cloudfunctions.functions.create",
    "cloudfunctions.functions.update",
    "compute.instances.create",
    "run.services.create",
]


def generate_bucket_names(keyword):
    """Generate bucket name permutations from a keyword."""
    return [perm.format(keyword=keyword) for perm in BUCKET_PERMUTATIONS]


def check_bucket_exists(bucket_name):
    """Check if a GCS bucket exists and test access permissions."""
    result = subprocess.run(
        ["gsutil", "ls", f"gs://{bucket_name}/"],
        capture_output=True, text=True, timeout=15
    )
    if result.returncode == 0:
        return {"exists": True, "accessible": True, "listing": result.stdout[:500]}
    if "AccessDeniedException" in result.stderr:
        return {"exists": True, "accessible": False, "error": "Access denied"}
    return {"exists": False, "accessible": False}


def test_bucket_permissions(bucket_name):
    """Test IAM permissions on a discovered bucket using gsutil."""
    permissions_to_test = [
        "storage.objects.list", "storage.objects.get", "storage.objects.create",
        "storage.objects.delete", "storage.buckets.getIamPolicy",
        "storage.buckets.setIamPolicy",
    ]
    result = subprocess.run(
        ["gcloud", "storage", "buckets", "get-iam-policy", f"gs://{bucket_name}",
         "--format=json"],
        capture_output=True, text=True, timeout=15
    )
    granted = []
    if result.returncode == 0:
        try:
            policy = json.loads(result.stdout)
            for binding in policy.get("bindings", []):
                if "allUsers" in binding.get("members", []) or "allAuthenticatedUsers" in binding.get("members", []):
                    granted.append({
                        "role": binding["role"],
                        "members": binding["members"],
                        "severity": "critical",
                    })
        except json.JSONDecodeError:
            pass
    return {"bucket": bucket_name, "public_bindings": granted}


def enumerate_iam_bindings(project_id):
    """Enumerate project-level IAM bindings for overly permissive roles."""
    result = subprocess.run(
        ["gcloud", "projects", "get-iam-policy", project_id, "--format=json"],
        capture_output=True, text=True, timeout=30
    )
    if result.returncode != 0:
        return {"error": result.stderr[:200]}
    policy = json.loads(result.stdout)
    findings = []
    risky_roles = {"roles/owner", "roles/editor", "roles/iam.securityAdmin",
                   "roles/iam.serviceAccountAdmin", "roles/storage.admin"}
    for binding in policy.get("bindings", []):
        if binding["role"] in risky_roles:
            findings.append({
                "role": binding["role"],
                "members": binding["members"],
                "member_count": len(binding["members"]),
                "severity": "high",
                "finding": "Overly permissive role binding",
            })
    return {"project": project_id, "risky_bindings": findings}


def check_service_account_keys(project_id):
    """Check for user-managed service account keys."""
    result = subprocess.run(
        ["gcloud", "iam", "service-accounts", "list",
         "--project", project_id, "--format=json"],
        capture_output=True, text=True, timeout=20
    )
    if result.returncode != 0:
        return {"error": result.stderr[:200]}
    accounts = json.loads(result.stdout)
    findings = []
    for sa in accounts:
        email = sa.get("email", "")
        keys_result = subprocess.run(
            ["gcloud", "iam", "service-accounts", "keys", "list",
             "--iam-account", email, "--format=json", "--managed-by=user"],
            capture_output=True, text=True, timeout=15
        )
        if keys_result.returncode == 0:
            keys = json.loads(keys_result.stdout)
            if keys:
                findings.append({
                    "service_account": email,
                    "user_managed_keys": len(keys),
                    "severity": "high",
                    "finding": "User-managed keys found (potential for key theft)",
                })
    return {"service_account_findings": findings}


def check_priv_esc_paths(project_id):
    """Check testable IAM permissions for privilege escalation vectors."""
    result = subprocess.run(
        ["gcloud", "projects", "get-iam-policy", project_id, "--format=json"],
        capture_output=True, text=True, timeout=20
    )
    if result.returncode != 0:
        return []
    policy = json.loads(result.stdout)
    esc_findings = []
    for binding in policy.get("bindings", []):
        role = binding["role"]
        role_result = subprocess.run(
            ["gcloud", "iam", "roles", "describe", role, "--format=json"],
            capture_output=True, text=True, timeout=10
        )
        if role_result.returncode == 0:
            role_info = json.loads(role_result.stdout)
            perms = set(role_info.get("includedPermissions", []))
            esc_perms = perms.intersection(PRIV_ESC_PERMISSIONS)
            if esc_perms:
                esc_findings.append({
                    "role": role,
                    "members": binding["members"],
                    "escalation_permissions": list(esc_perms),
                    "severity": "critical",
                })
    return esc_findings


def generate_report(buckets, iam_findings, sa_findings, esc_paths, keyword, project_id):
    """Generate GCP penetration test findings report."""
    return {
        "report_time": datetime.utcnow().isoformat(),
        "target_keyword": keyword,
        "target_project": project_id,
        "bucket_enumeration": buckets,
        "iam_audit": iam_findings,
        "service_account_audit": sa_findings,
        "privilege_escalation_paths": esc_paths,
        "total_findings": (
            len([b for b in buckets if b.get("exists")]) +
            len(iam_findings.get("risky_bindings", [])) +
            len(sa_findings.get("service_account_findings", [])) +
            len(esc_paths)
        ),
    }


def main():
    parser = argparse.ArgumentParser(description="GCP Penetration Testing Agent")
    parser.add_argument("--keyword", required=True, help="Keyword for bucket name permutation")
    parser.add_argument("--project", help="GCP project ID for IAM audit")
    parser.add_argument("--output", default="gcp_pentest_report.json")
    parser.add_argument("--skip-buckets", action="store_true", help="Skip bucket enumeration")
    args = parser.parse_args()

    buckets = []
    if not args.skip_buckets:
        names = generate_bucket_names(args.keyword)
        print(f"[*] Testing {len(names)} bucket permutations for '{args.keyword}'")
        for name in names:
            status = check_bucket_exists(name)
            if status["exists"]:
                perms = test_bucket_permissions(name)
                status.update(perms)
                buckets.append({"name": name, **status})
                print(f"  [!] Found: gs://{name} (accessible={status['accessible']})")

    iam_findings, sa_findings, esc_paths = {}, {}, []
    if args.project:
        print(f"[*] Auditing IAM for project: {args.project}")
        iam_findings = enumerate_iam_bindings(args.project)
        sa_findings = check_service_account_keys(args.project)
        esc_paths = check_priv_esc_paths(args.project)

    report = generate_report(buckets, iam_findings, sa_findings, esc_paths, args.keyword, args.project)
    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Report saved to {args.output} ({report['total_findings']} findings)")


if __name__ == "__main__":
    main()
