Files
Anthropic-Cybersecurity-Skills/skills/securing-api-gateway-with-aws-waf/scripts/agent.py
T
mukul975 27c6414ca5 Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills:
- scripts/agent.py: 80-150 line Python agents using real libraries (impacket,
  boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.)
- references/api-reference.md: real API documentation with method signatures
- LICENSE: MIT license for all skill folders
2026-03-10 21:02:12 +01:00

170 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""Agent for managing AWS WAF Web ACLs protecting API Gateway endpoints."""
import boto3
import json
import sys
import argparse
from datetime import datetime, timedelta, timezone
def get_waf_client(region="us-east-1"):
"""Create WAFv2 client for regional resources."""
return boto3.client("wafv2", region_name=region)
def list_web_acls(client, scope="REGIONAL"):
"""List all Web ACLs in the account."""
response = client.list_web_acls(Scope=scope)
acls = response.get("WebACLs", [])
print(f"[*] Found {len(acls)} Web ACL(s)")
for acl in acls:
print(f" - {acl['Name']} (ID: {acl['Id']})")
return acls
def get_web_acl_details(client, name, acl_id, scope="REGIONAL"):
"""Get detailed Web ACL configuration including all rules."""
response = client.get_web_acl(Name=name, Scope=scope, Id=acl_id)
acl = response["WebACL"]
lock_token = response["LockToken"]
print(f"\n[*] Web ACL: {acl['Name']}")
print(f" ARN: {acl['ARN']}")
print(f" Default Action: {list(acl['DefaultAction'].keys())[0]}")
print(f" Rules: {len(acl.get('Rules', []))}")
for rule in acl.get("Rules", []):
action = list(rule.get("Action", rule.get("OverrideAction", {})).keys())[0]
print(f" [{rule['Priority']}] {rule['Name']} -> {action}")
return acl, lock_token
def check_waf_association(client, web_acl_arn):
"""Check which resources are associated with a Web ACL."""
resource_types = ["API_GATEWAY", "APPLICATION_LOAD_BALANCER", "APPSYNC", "COGNITO_USER_POOL"]
associations = []
for rt in resource_types:
try:
resp = client.list_resources_for_web_acl(WebACLArn=web_acl_arn, ResourceType=rt)
for arn in resp.get("ResourceArns", []):
associations.append({"type": rt, "arn": arn})
print(f" [+] Associated: {rt} -> {arn}")
except client.exceptions.WAFInvalidParameterException:
continue
if not associations:
print(" [-] No resources associated with this Web ACL")
return associations
def get_sampled_requests(client, web_acl_arn, rule_metric, scope="REGIONAL", minutes=60):
"""Get sampled requests for a specific rule to analyze blocks."""
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(minutes=minutes)
try:
response = client.get_sampled_requests(
WebAclArn=web_acl_arn, RuleMetricName=rule_metric, Scope=scope,
TimeWindow={"StartTime": start_time, "EndTime": end_time}, MaxItems=50,
)
samples = response.get("SampledRequests", [])
print(f"\n[*] Sampled requests for rule '{rule_metric}': {len(samples)}")
for s in samples[:10]:
req = s["Request"]
print(f" [{s.get('Action', '?')}] {req['Method']} {req.get('URI', '/')} "
f"from {req.get('ClientIP', '?')} at {s.get('Timestamp', '?')}")
return samples
except Exception as e:
print(f" [-] Error getting samples: {e}")
return []
def get_rate_based_keys(client, name, acl_id, rule_name, scope="REGIONAL"):
"""Get IPs currently rate-limited by a rate-based rule."""
try:
response = client.get_rate_based_statement_managed_keys(
Scope=scope, WebACLName=name, WebACLId=acl_id, RuleName=rule_name,
)
keys = response.get("ManagedKeysIPV4", {}).get("Addresses", [])
keys += response.get("ManagedKeysIPV6", {}).get("Addresses", [])
print(f"\n[*] Rate-limited IPs for rule '{rule_name}': {len(keys)}")
for ip in keys:
print(f" [!] {ip}")
return keys
except Exception as e:
print(f" [-] Error: {e}")
return []
def get_waf_metrics(region="us-east-1", acl_name="api-gateway-waf", hours=24):
"""Get WAF CloudWatch metrics for blocked and allowed requests."""
cw = boto3.client("cloudwatch", region_name=region)
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(hours=hours)
metrics = {}
for metric_name in ["AllowedRequests", "BlockedRequests"]:
response = cw.get_metric_statistics(
Namespace="AWS/WAFV2", MetricName=metric_name,
Dimensions=[{"Name": "WebACL", "Value": acl_name}, {"Name": "Rule", "Value": "ALL"}],
StartTime=start_time, EndTime=end_time, Period=3600, Statistics=["Sum"],
)
total = sum(dp["Sum"] for dp in response.get("Datapoints", []))
metrics[metric_name] = total
print(f" {metric_name}: {int(total):,}")
return metrics
def audit_web_acl(client, name, acl_id, scope="REGIONAL"):
"""Run a security audit on a Web ACL configuration."""
acl, _ = get_web_acl_details(client, name, acl_id, scope)
findings = []
rules = acl.get("Rules", [])
rule_names = [r["Name"] for r in rules]
recommended = ["AWSManagedRulesCommonRuleSet", "AWSManagedRulesKnownBadInputsRuleSet",
"AWSManagedRulesSQLiRuleSet", "AWSManagedRulesAmazonIpReputationList"]
for rec in recommended:
if not any(rec in rn for rn in rule_names):
findings.append(f"MISSING: Recommended managed rule group '{rec}' not found")
has_rate_rule = any("RateBasedStatement" in json.dumps(r.get("Statement", {})) for r in rules)
if not has_rate_rule:
findings.append("MISSING: No rate-based rule configured")
default_action = list(acl["DefaultAction"].keys())[0]
if default_action == "Allow":
print(" [INFO] Default action is Allow (standard for WAF)")
print(f"\n[*] Audit findings: {len(findings)}")
for f in findings:
print(f" [!] {f}")
return findings
def main():
parser = argparse.ArgumentParser(description="AWS WAF API Gateway Security Agent")
parser.add_argument("action", choices=["list", "details", "audit", "metrics", "samples", "rate-keys"])
parser.add_argument("--name", help="Web ACL name")
parser.add_argument("--id", help="Web ACL ID")
parser.add_argument("--region", default="us-east-1")
parser.add_argument("--rule-metric", help="Rule metric name for sampling")
parser.add_argument("--rule-name", help="Rate-based rule name")
parser.add_argument("--hours", type=int, default=24, help="Lookback hours for metrics")
args = parser.parse_args()
client = get_waf_client(args.region)
if args.action == "list":
list_web_acls(client)
elif args.action == "details":
acl, _ = get_web_acl_details(client, args.name, args.id)
check_waf_association(client, acl["ARN"])
elif args.action == "audit":
audit_web_acl(client, args.name, args.id)
elif args.action == "metrics":
get_waf_metrics(args.region, args.name or "api-gateway-waf", args.hours)
elif args.action == "samples":
acls = list_web_acls(client)
acl_arn = next((a["ARN"] for a in acls if a["Name"] == args.name), None)
if acl_arn:
get_sampled_requests(client, acl_arn, args.rule_metric or "ALL")
elif args.action == "rate-keys":
get_rate_based_keys(client, args.name, args.id, args.rule_name or "RateLimitPerIP")
if __name__ == "__main__":
main()