#!/usr/bin/env python3
"""Agent for auditing serverless function security across AWS Lambda."""

import boto3
import json
import argparse
from datetime import datetime


def list_functions(region="us-east-1"):
    """List all Lambda functions with security-relevant configuration."""
    lam = boto3.client("lambda", region_name=region)
    functions = []
    paginator = lam.get_paginator("list_functions")
    for page in paginator.paginate():
        for f in page["Functions"]:
            functions.append({
                "name": f["FunctionName"],
                "runtime": f.get("Runtime", "N/A"),
                "role": f["Role"].split("/")[-1],
                "timeout": f.get("Timeout", 3),
                "memory": f.get("MemorySize", 128),
                "kms_key": f.get("KMSKeyArn", "None (default)"),
                "vpc": bool(f.get("VpcConfig", {}).get("SubnetIds")),
            })
    print(f"[*] Found {len(functions)} Lambda functions")
    for fn in functions:
        print(f"  {fn['name']} | {fn['runtime']} | role={fn['role']} | VPC={fn['vpc']}")
    return functions


def check_function_urls(region="us-east-1"):
    """Check for Lambda function URLs with insecure authentication."""
    lam = boto3.client("lambda", region_name=region)
    findings = []
    paginator = lam.get_paginator("list_functions")
    for page in paginator.paginate():
        for f in page["Functions"]:
            try:
                url_config = lam.get_function_url_config(FunctionName=f["FunctionName"])
                auth_type = url_config.get("AuthType", "NONE")
                if auth_type == "NONE":
                    findings.append({
                        "function": f["FunctionName"],
                        "url": url_config.get("FunctionUrl", ""),
                        "auth_type": auth_type,
                        "severity": "CRITICAL",
                    })
                    print(f"  [!] CRITICAL: {f['FunctionName']} has unauthenticated URL: "
                          f"{url_config.get('FunctionUrl', '')}")
                else:
                    print(f"  [+] {f['FunctionName']}: URL auth={auth_type}")
            except lam.exceptions.ResourceNotFoundException:
                continue
    print(f"[*] {len(findings)} functions with unauthenticated URLs")
    return findings


def check_env_variables(region="us-east-1"):
    """Scan Lambda environment variables for potential hardcoded secrets."""
    lam = boto3.client("lambda", region_name=region)
    findings = []
    secret_patterns = ["password", "secret", "api_key", "apikey", "token", "private_key",
                       "access_key", "db_pass", "database_url", "smtp"]
    paginator = lam.get_paginator("list_functions")
    for page in paginator.paginate():
        for f in page["Functions"]:
            env_vars = f.get("Environment", {}).get("Variables", {})
            kms_key = f.get("KMSKeyArn")
            for key, value in env_vars.items():
                key_lower = key.lower()
                if any(p in key_lower for p in secret_patterns):
                    has_kms = bool(kms_key)
                    findings.append({
                        "function": f["FunctionName"],
                        "variable": key,
                        "encrypted": has_kms,
                        "severity": "HIGH" if not has_kms else "MEDIUM",
                    })
                    enc_status = "KMS-encrypted" if has_kms else "PLAINTEXT"
                    print(f"  [!] {f['FunctionName']}: {key} ({enc_status})")
    print(f"[*] {len(findings)} potential secrets in environment variables")
    return findings


def check_shared_roles(region="us-east-1"):
    """Identify Lambda functions sharing the same execution role."""
    lam = boto3.client("lambda", region_name=region)
    role_map = {}
    paginator = lam.get_paginator("list_functions")
    for page in paginator.paginate():
        for f in page["Functions"]:
            role = f["Role"]
            role_name = role.split("/")[-1]
            if role_name not in role_map:
                role_map[role_name] = []
            role_map[role_name].append(f["FunctionName"])
    findings = []
    print("\n[*] Checking for shared execution roles...")
    for role, funcs in role_map.items():
        if len(funcs) > 1:
            findings.append({"role": role, "functions": funcs, "count": len(funcs)})
            print(f"  [!] Role '{role}' shared by {len(funcs)} functions: {', '.join(funcs[:5])}")
    print(f"[*] {len(findings)} shared roles found")
    return findings


def check_reserved_concurrency(region="us-east-1"):
    """Check if functions have reserved concurrency set to prevent resource exhaustion."""
    lam = boto3.client("lambda", region_name=region)
    no_concurrency = []
    paginator = lam.get_paginator("list_functions")
    for page in paginator.paginate():
        for f in page["Functions"]:
            try:
                conc = lam.get_function_concurrency(FunctionName=f["FunctionName"])
                reserved = conc.get("ReservedConcurrentExecutions")
                if reserved is None:
                    no_concurrency.append(f["FunctionName"])
            except Exception:
                no_concurrency.append(f["FunctionName"])
    if no_concurrency:
        print(f"\n[*] {len(no_concurrency)} functions without reserved concurrency")
    return no_concurrency


def full_audit(region="us-east-1", output_path="serverless_audit.json"):
    """Run comprehensive serverless security audit."""
    print("[*] Starting serverless security audit...\n")
    report = {
        "audit_date": datetime.now().isoformat(),
        "region": region,
        "functions": list_functions(region),
        "unauthenticated_urls": check_function_urls(region),
        "env_secrets": check_env_variables(region),
        "shared_roles": check_shared_roles(region),
        "no_concurrency_limit": check_reserved_concurrency(region),
    }
    total_findings = (len(report["unauthenticated_urls"]) + len(report["env_secrets"]) +
                      len(report["shared_roles"]))
    report["total_findings"] = total_findings
    with open(output_path, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"\n[*] Audit complete: {total_findings} findings")
    print(f"[*] Report saved to {output_path}")


def main():
    parser = argparse.ArgumentParser(description="Serverless Function Security Agent")
    parser.add_argument("action", choices=["list", "urls", "env-secrets", "shared-roles",
                                           "concurrency", "full-audit"])
    parser.add_argument("--region", default="us-east-1")
    parser.add_argument("-o", "--output", default="serverless_audit.json")
    args = parser.parse_args()

    if args.action == "list":
        list_functions(args.region)
    elif args.action == "urls":
        check_function_urls(args.region)
    elif args.action == "env-secrets":
        check_env_variables(args.region)
    elif args.action == "shared-roles":
        check_shared_roles(args.region)
    elif args.action == "concurrency":
        check_reserved_concurrency(args.region)
    elif args.action == "full-audit":
        full_audit(args.region, args.output)


if __name__ == "__main__":
    main()
