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,60 +1,327 @@
#!/usr/bin/env python3
"""Cloud incident containment agent."""
import argparse, json
"""AWS cloud incident containment agent.
Automates incident containment procedures in AWS environments including
EC2 instance isolation, IAM credential revocation, security group lockdown,
S3 bucket access restriction, and forensic snapshot creation using boto3.
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone
try:
import requests
import boto3
from botocore.exceptions import ClientError
except ImportError:
requests = None
print("[!] 'boto3' library required: pip install boto3", file=sys.stderr)
sys.exit(1)
def run_scan(target, token=None):
def get_session(profile=None, region=None):
"""Create a boto3 session."""
kwargs = {}
if profile:
kwargs["profile_name"] = profile
if region:
kwargs["region_name"] = region
return boto3.Session(**kwargs)
def isolate_ec2_instance(session, instance_id, vpc_id=None):
"""Isolate an EC2 instance by replacing security groups with a deny-all SG."""
ec2 = session.client("ec2")
findings = []
if not requests: return [{"error": "requests required"}]
headers = {"Authorization": f"Bearer {token}"} if token else {}
print(f"[*] Isolating EC2 instance: {instance_id}")
# Get current instance details
try:
resp = requests.get(f"{target}", headers=headers, timeout=15)
if resp.status_code == 200:
findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"})
resp = ec2.describe_instances(InstanceIds=[instance_id])
instance = resp["Reservations"][0]["Instances"][0]
current_sgs = [sg["GroupId"] for sg in instance.get("SecurityGroups", [])]
instance_vpc = instance.get("VpcId", vpc_id)
findings.append({"action": "describe_instance", "status": "OK",
"detail": f"Current SGs: {current_sgs}"})
except ClientError as e:
findings.append({"action": "describe_instance", "status": "FAIL",
"severity": "CRITICAL", "detail": str(e)})
return findings
# Create or find isolation security group
isolation_sg_name = f"incident-isolation-{instance_id[:8]}"
isolation_sg_id = None
try:
existing = ec2.describe_security_groups(
Filters=[{"Name": "group-name", "Values": [isolation_sg_name]},
{"Name": "vpc-id", "Values": [instance_vpc]}]
)
if existing["SecurityGroups"]:
isolation_sg_id = existing["SecurityGroups"][0]["GroupId"]
else:
findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"})
except requests.RequestException as e:
findings.append({"error": str(e)})
resp = ec2.create_security_group(
GroupName=isolation_sg_name,
Description=f"Incident isolation SG for {instance_id}",
VpcId=instance_vpc,
)
isolation_sg_id = resp["GroupId"]
# Revoke default egress rule
ec2.revoke_security_group_egress(
GroupId=isolation_sg_id,
IpPermissions=[{"IpProtocol": "-1", "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}],
)
findings.append({"action": "create_isolation_sg", "status": "OK",
"detail": f"SG: {isolation_sg_id} (no ingress, no egress)"})
except ClientError as e:
findings.append({"action": "create_isolation_sg", "status": "FAIL",
"severity": "HIGH", "detail": str(e)})
return findings
# Replace security groups with isolation SG
try:
ec2.modify_instance_attribute(
InstanceId=instance_id,
Groups=[isolation_sg_id],
)
findings.append({"action": "apply_isolation_sg", "status": "OK",
"detail": f"Replaced {current_sgs} with [{isolation_sg_id}]"})
except ClientError as e:
findings.append({"action": "apply_isolation_sg", "status": "FAIL",
"severity": "CRITICAL", "detail": str(e)})
# Tag instance as contained
try:
ec2.create_tags(
Resources=[instance_id],
Tags=[
{"Key": "IncidentStatus", "Value": "CONTAINED"},
{"Key": "ContainmentTime", "Value": datetime.now(timezone.utc).isoformat()},
{"Key": "OriginalSecurityGroups", "Value": ",".join(current_sgs)},
],
)
findings.append({"action": "tag_instance", "status": "OK"})
except ClientError as e:
findings.append({"action": "tag_instance", "status": "FAIL", "detail": str(e)})
return findings
def analyze_results(target, token=None):
def create_forensic_snapshot(session, instance_id):
"""Create EBS snapshots for forensic preservation."""
ec2 = session.client("ec2")
findings = []
if not requests: return []
headers = {"Authorization": f"Bearer {token}"} if token else {}
print(f"[*] Creating forensic snapshots for: {instance_id}")
try:
resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15)
if resp.status_code == 200:
data = resp.json()
for item in data.get("findings", data.get("results", [])):
severity = item.get("severity", item.get("risk", "MEDIUM"))
findings.append({"check": item.get("name", item.get("title", "unknown")),
"severity": severity.upper() if isinstance(severity, str) else "MEDIUM"})
except requests.RequestException:
pass
resp = ec2.describe_instances(InstanceIds=[instance_id])
instance = resp["Reservations"][0]["Instances"][0]
volumes = []
for mapping in instance.get("BlockDeviceMappings", []):
vol_id = mapping.get("Ebs", {}).get("VolumeId")
if vol_id:
volumes.append((mapping["DeviceName"], vol_id))
except ClientError as e:
findings.append({"action": "describe_volumes", "status": "FAIL", "detail": str(e)})
return findings
for device_name, vol_id in volumes:
try:
snap = ec2.create_snapshot(
VolumeId=vol_id,
Description=f"Forensic snapshot - {instance_id} {device_name} - "
f"{datetime.now(timezone.utc).isoformat()}",
TagSpecifications=[{
"ResourceType": "snapshot",
"Tags": [
{"Key": "Purpose", "Value": "Forensic-Preservation"},
{"Key": "SourceInstance", "Value": instance_id},
{"Key": "SourceVolume", "Value": vol_id},
{"Key": "CreatedBy", "Value": "incident-containment-agent"},
],
}],
)
findings.append({
"action": "create_snapshot",
"status": "OK",
"detail": f"{vol_id} ({device_name}) -> {snap['SnapshotId']}",
})
except ClientError as e:
findings.append({"action": "create_snapshot", "status": "FAIL",
"detail": f"{vol_id}: {e}"})
return findings
def revoke_iam_credentials(session, username):
"""Revoke all IAM credentials for a compromised user."""
iam = session.client("iam")
findings = []
print(f"[*] Revoking credentials for IAM user: {username}")
# Deactivate access keys
try:
keys = iam.list_access_keys(UserName=username)
for key in keys.get("AccessKeyMetadata", []):
key_id = key["AccessKeyId"]
iam.update_access_key(
UserName=username, AccessKeyId=key_id, Status="Inactive"
)
findings.append({"action": "deactivate_access_key", "status": "OK",
"detail": f"Key {key_id[:8]}... deactivated"})
except ClientError as e:
findings.append({"action": "deactivate_access_keys", "status": "FAIL", "detail": str(e)})
# Invalidate console session by attaching deny-all inline policy
deny_policy = json.dumps({
"Version": "2012-10-17",
"Statement": [{"Effect": "Deny", "Action": "*", "Resource": "*"}],
})
try:
iam.put_user_policy(
UserName=username,
PolicyName="IncidentDenyAll",
PolicyDocument=deny_policy,
)
findings.append({"action": "attach_deny_policy", "status": "OK",
"detail": "Deny-all policy attached"})
except ClientError as e:
findings.append({"action": "attach_deny_policy", "status": "FAIL", "detail": str(e)})
# Delete login profile (console access)
try:
iam.delete_login_profile(UserName=username)
findings.append({"action": "delete_console_access", "status": "OK"})
except iam.exceptions.NoSuchEntityException:
findings.append({"action": "delete_console_access", "status": "SKIP",
"detail": "No console access configured"})
except ClientError as e:
findings.append({"action": "delete_console_access", "status": "FAIL", "detail": str(e)})
return findings
def restrict_s3_bucket(session, bucket_name):
"""Restrict S3 bucket access during incident containment."""
s3 = session.client("s3")
findings = []
print(f"[*] Restricting S3 bucket: {bucket_name}")
# Block public access
try:
s3.put_public_access_block(
Bucket=bucket_name,
PublicAccessBlockConfiguration={
"BlockPublicAcls": True,
"IgnorePublicAcls": True,
"BlockPublicPolicy": True,
"RestrictPublicBuckets": True,
},
)
findings.append({"action": "block_public_access", "status": "OK"})
except ClientError as e:
findings.append({"action": "block_public_access", "status": "FAIL", "detail": str(e)})
# Enable versioning (preserve evidence)
try:
s3.put_bucket_versioning(
Bucket=bucket_name,
VersioningConfiguration={"Status": "Enabled"},
)
findings.append({"action": "enable_versioning", "status": "OK"})
except ClientError as e:
findings.append({"action": "enable_versioning", "status": "FAIL", "detail": str(e)})
return findings
def format_summary(all_actions):
"""Print containment summary."""
print(f"\n{'='*60}")
print(f" Cloud Incident Containment Report")
print(f"{'='*60}")
success = sum(1 for a in all_actions if a.get("status") == "OK")
failed = sum(1 for a in all_actions if a.get("status") == "FAIL")
print(f" Actions : {len(all_actions)}")
print(f" Success : {success}")
print(f" Failed : {failed}")
print(f"\n Actions Taken:")
for a in all_actions:
icon = "OK" if a["status"] == "OK" else "!!" if a["status"] == "FAIL" else "--"
print(f" [{icon}] {a['action']:30s} {a.get('detail', '')[:50]}")
def main():
p = argparse.ArgumentParser(description="Cloud incident containment agent")
p.add_argument("--target", required=True, help="Target URL or IP")
p.add_argument("--token", 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("[*] Cloud incident containment agent")
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []}
report["findings"].extend(run_scan(a.target, a.token))
report["findings"].extend(analyze_results(a.target, a.token))
high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL"))
report["risk_level"] = "CRITICAL" if high > 2 else "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="AWS cloud incident containment agent"
)
sub = parser.add_subparsers(dest="command")
p_iso = sub.add_parser("isolate", help="Isolate EC2 instance")
p_iso.add_argument("--instance-id", required=True)
p_snap = sub.add_parser("snapshot", help="Create forensic snapshots")
p_snap.add_argument("--instance-id", required=True)
p_iam = sub.add_parser("revoke-iam", help="Revoke IAM user credentials")
p_iam.add_argument("--username", required=True)
p_s3 = sub.add_parser("restrict-s3", help="Restrict S3 bucket")
p_s3.add_argument("--bucket", required=True)
p_full = sub.add_parser("full-contain", help="Full containment: isolate + snapshot + IAM")
p_full.add_argument("--instance-id", required=True)
p_full.add_argument("--username", help="IAM user to revoke")
p_full.add_argument("--bucket", help="S3 bucket to restrict")
parser.add_argument("--profile", help="AWS CLI profile")
parser.add_argument("--region", help="AWS region")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
session = get_session(args.profile, args.region)
all_actions = []
if args.command == "isolate":
all_actions.extend(isolate_ec2_instance(session, args.instance_id))
elif args.command == "snapshot":
all_actions.extend(create_forensic_snapshot(session, args.instance_id))
elif args.command == "revoke-iam":
all_actions.extend(revoke_iam_credentials(session, args.username))
elif args.command == "restrict-s3":
all_actions.extend(restrict_s3_bucket(session, args.bucket))
elif args.command == "full-contain":
all_actions.extend(isolate_ec2_instance(session, args.instance_id))
all_actions.extend(create_forensic_snapshot(session, args.instance_id))
if args.username:
all_actions.extend(revoke_iam_credentials(session, args.username))
if args.bucket:
all_actions.extend(restrict_s3_bucket(session, args.bucket))
format_summary(all_actions)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "AWS Incident Containment",
"command": args.command,
"actions": all_actions,
"success_count": sum(1 for a in all_actions if a["status"] == "OK"),
"fail_count": sum(1 for a in all_actions if a["status"] == "FAIL"),
}
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()