mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 14:14:56 +03:00
328 lines
12 KiB
Python
328 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""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 boto3
|
|
from botocore.exceptions import ClientError
|
|
except ImportError:
|
|
print("[!] 'boto3' library required: pip install boto3", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
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 = []
|
|
print(f"[*] Isolating EC2 instance: {instance_id}")
|
|
|
|
# Get current instance details
|
|
try:
|
|
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:
|
|
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 create_forensic_snapshot(session, instance_id):
|
|
"""Create EBS snapshots for forensic preservation."""
|
|
ec2 = session.client("ec2")
|
|
findings = []
|
|
print(f"[*] Creating forensic snapshots for: {instance_id}")
|
|
|
|
try:
|
|
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():
|
|
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()
|