mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-13 06:34:57 +03:00
Add 30 new production-grade cybersecurity skills: AI security, supply chain, firmware, cloud-native, compliance, deception, crypto, threat hunting, purple team, OT, privacy
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
#!/usr/bin/env python3
|
||||
# For authorized cloud security assessments only
|
||||
"""AWS Nitro Enclave Security Agent - Validates enclave attestation, audits KMS policies, and verifies enclave isolation."""
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
try:
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError
|
||||
except ImportError:
|
||||
print("ERROR: boto3 required. Install with: pip install boto3")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import cbor2
|
||||
except ImportError:
|
||||
cbor2 = None
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_nitro_instances(ec2_client, region):
|
||||
"""Find EC2 instances with Nitro Enclave support enabled."""
|
||||
findings = []
|
||||
paginator = ec2_client.get_paginator("describe_instances")
|
||||
for page in paginator.paginate(
|
||||
Filters=[{"Name": "enclave-options.enabled", "Values": ["true"]}]
|
||||
):
|
||||
for reservation in page["Reservations"]:
|
||||
for instance in reservation["Instances"]:
|
||||
instance_info = {
|
||||
"instance_id": instance["InstanceId"],
|
||||
"instance_type": instance["InstanceType"],
|
||||
"state": instance["State"]["Name"],
|
||||
"enclave_enabled": True,
|
||||
"iam_role": None,
|
||||
"launch_time": instance.get("LaunchTime", "").isoformat() if instance.get("LaunchTime") else None,
|
||||
"region": region,
|
||||
}
|
||||
if instance.get("IamInstanceProfile"):
|
||||
instance_info["iam_role"] = instance["IamInstanceProfile"]["Arn"]
|
||||
findings.append(instance_info)
|
||||
logger.info("Found %d Nitro Enclave-enabled instances in %s", len(findings), region)
|
||||
return findings
|
||||
|
||||
|
||||
def audit_kms_key_policy(kms_client, key_id):
|
||||
"""Audit a KMS key policy for Nitro Enclave attestation conditions."""
|
||||
result = {
|
||||
"key_id": key_id,
|
||||
"has_attestation_condition": False,
|
||||
"pcr_conditions": [],
|
||||
"image_sha_condition": False,
|
||||
"allowed_principals": [],
|
||||
"allowed_actions": [],
|
||||
"issues": [],
|
||||
}
|
||||
try:
|
||||
key_meta = kms_client.describe_key(KeyId=key_id)
|
||||
result["key_arn"] = key_meta["KeyMetadata"]["Arn"]
|
||||
result["key_state"] = key_meta["KeyMetadata"]["KeyState"]
|
||||
result["key_usage"] = key_meta["KeyMetadata"]["KeyUsage"]
|
||||
|
||||
policy_json = kms_client.get_key_policy(KeyId=key_id, PolicyName="default")["Policy"]
|
||||
policy = json.loads(policy_json)
|
||||
|
||||
for statement in policy.get("Statement", []):
|
||||
principals = statement.get("Principal", {})
|
||||
actions = statement.get("Action", [])
|
||||
if isinstance(actions, str):
|
||||
actions = [actions]
|
||||
conditions = statement.get("Condition", {})
|
||||
|
||||
for action in actions:
|
||||
if action not in result["allowed_actions"]:
|
||||
result["allowed_actions"].append(action)
|
||||
|
||||
if isinstance(principals, dict) and "AWS" in principals:
|
||||
aws_principals = principals["AWS"]
|
||||
if isinstance(aws_principals, str):
|
||||
aws_principals = [aws_principals]
|
||||
result["allowed_principals"].extend(aws_principals)
|
||||
|
||||
# Check for attestation conditions
|
||||
for operator_key, operator_conditions in conditions.items():
|
||||
for cond_key, cond_value in operator_conditions.items():
|
||||
if "RecipientAttestation" in cond_key:
|
||||
result["has_attestation_condition"] = True
|
||||
if "ImageSha384" in cond_key:
|
||||
result["image_sha_condition"] = True
|
||||
result["pcr_conditions"].append({
|
||||
"type": "ImageSha384 (PCR0)",
|
||||
"operator": operator_key,
|
||||
"value": cond_value[:32] + "..." if len(str(cond_value)) > 32 else cond_value,
|
||||
})
|
||||
elif "PCR" in cond_key:
|
||||
pcr_id = cond_key.split(":")[-1]
|
||||
result["pcr_conditions"].append({
|
||||
"type": pcr_id,
|
||||
"operator": operator_key,
|
||||
"value": cond_value[:32] + "..." if len(str(cond_value)) > 32 else cond_value,
|
||||
})
|
||||
|
||||
# Check for missing attestation on decrypt actions
|
||||
has_decrypt = any("Decrypt" in a or "GenerateDataKey" in a for a in actions)
|
||||
if has_decrypt and not any("RecipientAttestation" in str(conditions)):
|
||||
if statement.get("Effect") == "Allow":
|
||||
result["issues"].append(
|
||||
f"Statement '{statement.get('Sid', 'unnamed')}' allows Decrypt/GenerateDataKey "
|
||||
f"without kms:RecipientAttestation condition - parent instance can decrypt directly"
|
||||
)
|
||||
|
||||
if not result["has_attestation_condition"]:
|
||||
result["issues"].append(
|
||||
"KMS key policy has no RecipientAttestation conditions - "
|
||||
"decryption is not restricted to verified enclaves"
|
||||
)
|
||||
|
||||
except ClientError as e:
|
||||
result["issues"].append(f"Error accessing key: {e.response['Error']['Message']}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def audit_iam_role_for_enclave(iam_client, role_name):
|
||||
"""Check if an IAM role has appropriate permissions for enclave operations."""
|
||||
result = {
|
||||
"role_name": role_name,
|
||||
"has_kms_permissions": False,
|
||||
"kms_actions": [],
|
||||
"has_ec2_enclave_permissions": False,
|
||||
"overprivileged": False,
|
||||
"issues": [],
|
||||
}
|
||||
try:
|
||||
# Check attached policies
|
||||
attached = iam_client.list_attached_role_policies(RoleName=role_name)
|
||||
for policy in attached["AttachedPolicies"]:
|
||||
if policy["PolicyName"] == "AdministratorAccess":
|
||||
result["overprivileged"] = True
|
||||
result["issues"].append(
|
||||
"Role has AdministratorAccess - violates least privilege for enclave workloads"
|
||||
)
|
||||
|
||||
policy_version = iam_client.get_policy(PolicyArn=policy["PolicyArn"])
|
||||
version_id = policy_version["Policy"]["DefaultVersionId"]
|
||||
policy_doc = iam_client.get_policy_version(
|
||||
PolicyArn=policy["PolicyArn"], VersionId=version_id
|
||||
)
|
||||
for stmt in policy_doc["PolicyVersion"]["Document"].get("Statement", []):
|
||||
actions = stmt.get("Action", [])
|
||||
if isinstance(actions, str):
|
||||
actions = [actions]
|
||||
for action in actions:
|
||||
if "kms:" in action:
|
||||
result["has_kms_permissions"] = True
|
||||
result["kms_actions"].append(action)
|
||||
if action in ("kms:*", "*"):
|
||||
result["overprivileged"] = True
|
||||
result["issues"].append(
|
||||
f"Role has wildcard KMS permissions ({action}) - should restrict to specific keys"
|
||||
)
|
||||
|
||||
# Check inline policies
|
||||
inline = iam_client.list_role_policies(RoleName=role_name)
|
||||
for policy_name in inline["PolicyNames"]:
|
||||
policy_doc = iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
|
||||
for stmt in policy_doc["PolicyDocument"].get("Statement", []):
|
||||
actions = stmt.get("Action", [])
|
||||
if isinstance(actions, str):
|
||||
actions = [actions]
|
||||
resources = stmt.get("Resource", [])
|
||||
if isinstance(resources, str):
|
||||
resources = [resources]
|
||||
for action in actions:
|
||||
if "kms:" in action:
|
||||
result["has_kms_permissions"] = True
|
||||
result["kms_actions"].append(action)
|
||||
if "*" in resources:
|
||||
result["issues"].append(
|
||||
f"Inline policy '{policy_name}' uses wildcard Resource - restrict to specific KMS key ARNs"
|
||||
)
|
||||
|
||||
if not result["has_kms_permissions"]:
|
||||
result["issues"].append("Role has no KMS permissions - cannot perform enclave-side decryption")
|
||||
|
||||
except ClientError as e:
|
||||
result["issues"].append(f"Error auditing role: {e.response['Error']['Message']}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def check_enclave_allocator_config(instance_id, ssm_client):
|
||||
"""Check enclave allocator configuration via SSM (if available)."""
|
||||
result = {
|
||||
"instance_id": instance_id,
|
||||
"allocator_configured": False,
|
||||
"memory_mib": None,
|
||||
"cpu_count": None,
|
||||
"issues": [],
|
||||
}
|
||||
try:
|
||||
response = ssm_client.send_command(
|
||||
InstanceIds=[instance_id],
|
||||
DocumentName="AWS-RunShellScript",
|
||||
Parameters={
|
||||
"commands": ["cat /etc/nitro_enclaves/allocator.yaml 2>/dev/null || echo 'NOT_FOUND'"]
|
||||
},
|
||||
)
|
||||
command_id = response["Command"]["CommandId"]
|
||||
|
||||
import time
|
||||
time.sleep(3)
|
||||
|
||||
output = ssm_client.get_command_invocation(
|
||||
CommandId=command_id, InstanceId=instance_id
|
||||
)
|
||||
stdout = output.get("StandardOutputContent", "")
|
||||
|
||||
if "NOT_FOUND" in stdout:
|
||||
result["issues"].append("Allocator config not found at /etc/nitro_enclaves/allocator.yaml")
|
||||
else:
|
||||
result["allocator_configured"] = True
|
||||
for line in stdout.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("memory_mib:"):
|
||||
result["memory_mib"] = int(line.split(":")[1].strip())
|
||||
elif line.startswith("cpu_count:"):
|
||||
result["cpu_count"] = int(line.split(":")[1].strip())
|
||||
|
||||
if result["memory_mib"] and result["memory_mib"] < 512:
|
||||
result["issues"].append(
|
||||
f"Allocated memory ({result['memory_mib']} MiB) is very low - may cause enclave launch failures"
|
||||
)
|
||||
if result["cpu_count"] and result["cpu_count"] < 2:
|
||||
result["issues"].append(
|
||||
f"Allocated CPUs ({result['cpu_count']}) is minimal - consider 2+ for production"
|
||||
)
|
||||
|
||||
except ClientError as e:
|
||||
result["issues"].append(f"SSM access failed: {e.response['Error']['Message']}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def validate_attestation_document_structure(attestation_b64):
|
||||
"""Validate the structure of a base64-encoded attestation document."""
|
||||
if cbor2 is None:
|
||||
return {"error": "cbor2 package required for attestation validation. Install with: pip install cbor2"}
|
||||
|
||||
result = {
|
||||
"valid_structure": False,
|
||||
"pcrs": {},
|
||||
"module_id": None,
|
||||
"digest": None,
|
||||
"timestamp": None,
|
||||
"has_certificate": False,
|
||||
"has_cabundle": False,
|
||||
"has_public_key": False,
|
||||
"issues": [],
|
||||
}
|
||||
try:
|
||||
attestation_bytes = base64.b64decode(attestation_b64)
|
||||
|
||||
# COSE_Sign1 is a CBOR array: [protected, unprotected, payload, signature]
|
||||
cose_structure = cbor2.loads(attestation_bytes)
|
||||
if hasattr(cose_structure, "tag") and cose_structure.tag == 18:
|
||||
cose_array = cose_structure.value
|
||||
elif isinstance(cose_structure, list) and len(cose_structure) == 4:
|
||||
cose_array = cose_structure
|
||||
else:
|
||||
result["issues"].append("Not a valid COSE_Sign1 structure")
|
||||
return result
|
||||
|
||||
payload = cbor2.loads(cose_array[2])
|
||||
|
||||
result["module_id"] = payload.get("module_id")
|
||||
result["digest"] = payload.get("digest")
|
||||
result["timestamp"] = payload.get("timestamp")
|
||||
|
||||
if result["timestamp"]:
|
||||
ts = datetime.fromtimestamp(result["timestamp"] / 1000, tz=timezone.utc)
|
||||
result["timestamp_human"] = ts.isoformat()
|
||||
|
||||
pcrs = payload.get("pcrs", {})
|
||||
for idx, value in pcrs.items():
|
||||
result["pcrs"][f"PCR{idx}"] = value.hex() if isinstance(value, bytes) else str(value)
|
||||
|
||||
result["has_certificate"] = "certificate" in payload and payload["certificate"] is not None
|
||||
result["has_cabundle"] = "cabundle" in payload and len(payload.get("cabundle", [])) > 0
|
||||
result["has_public_key"] = "public_key" in payload and payload["public_key"] is not None
|
||||
|
||||
result["valid_structure"] = True
|
||||
|
||||
if not result["has_cabundle"]:
|
||||
result["issues"].append("Missing CA bundle - cannot verify certificate chain to AWS root")
|
||||
if not result["has_public_key"]:
|
||||
result["issues"].append("No public key in attestation - KMS cannot encrypt response to enclave")
|
||||
if "PCR0" not in result["pcrs"]:
|
||||
result["issues"].append("PCR0 (image hash) not present in attestation document")
|
||||
|
||||
except Exception as e:
|
||||
result["issues"].append(f"Attestation parsing error: {str(e)}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def audit_cloudtrail_enclave_events(cloudtrail_client, days_back=7):
|
||||
"""Search CloudTrail for enclave-related security events."""
|
||||
from datetime import timedelta
|
||||
end_time = datetime.now(timezone.utc)
|
||||
start_time = end_time - timedelta(days=days_back)
|
||||
|
||||
events_of_interest = [
|
||||
"RunInstances",
|
||||
"TerminateInstances",
|
||||
"ModifyInstanceAttribute",
|
||||
]
|
||||
kms_events = ["Decrypt", "GenerateDataKey", "GenerateDataKeyPair", "GenerateRandom"]
|
||||
|
||||
findings = []
|
||||
|
||||
# Check for instance launches with enclave options
|
||||
for event_name in events_of_interest:
|
||||
try:
|
||||
response = cloudtrail_client.lookup_events(
|
||||
LookupAttributes=[
|
||||
{"AttributeKey": "EventName", "AttributeValue": event_name}
|
||||
],
|
||||
StartTime=start_time,
|
||||
EndTime=end_time,
|
||||
MaxResults=50,
|
||||
)
|
||||
for event in response.get("Events", []):
|
||||
ct_event = json.loads(event.get("CloudTrailEvent", "{}"))
|
||||
req_params = ct_event.get("requestParameters", {})
|
||||
|
||||
if event_name == "RunInstances":
|
||||
enclave_opts = req_params.get("enclaveOptions", {})
|
||||
if enclave_opts.get("enabled"):
|
||||
findings.append({
|
||||
"event": event_name,
|
||||
"time": event["EventTime"].isoformat(),
|
||||
"user": event.get("Username"),
|
||||
"detail": "Enclave-enabled instance launched",
|
||||
"source_ip": ct_event.get("sourceIPAddress"),
|
||||
})
|
||||
except ClientError:
|
||||
continue
|
||||
|
||||
# Check for KMS calls with Recipient parameter (enclave attestation)
|
||||
for event_name in kms_events:
|
||||
try:
|
||||
response = cloudtrail_client.lookup_events(
|
||||
LookupAttributes=[
|
||||
{"AttributeKey": "EventName", "AttributeValue": event_name}
|
||||
],
|
||||
StartTime=start_time,
|
||||
EndTime=end_time,
|
||||
MaxResults=50,
|
||||
)
|
||||
for event in response.get("Events", []):
|
||||
ct_event = json.loads(event.get("CloudTrailEvent", "{}"))
|
||||
req_params = ct_event.get("requestParameters", {})
|
||||
if "recipient" in req_params or "Recipient" in req_params:
|
||||
findings.append({
|
||||
"event": event_name,
|
||||
"time": event["EventTime"].isoformat(),
|
||||
"user": event.get("Username"),
|
||||
"detail": "KMS operation with enclave attestation document",
|
||||
"key_id": req_params.get("keyId"),
|
||||
"source_ip": ct_event.get("sourceIPAddress"),
|
||||
})
|
||||
except ClientError:
|
||||
continue
|
||||
|
||||
logger.info("Found %d enclave-related CloudTrail events", len(findings))
|
||||
return findings
|
||||
|
||||
|
||||
def generate_report(instances, kms_audits, iam_audits, cloudtrail_events, attestation_results=None):
|
||||
"""Generate comprehensive Nitro Enclave security assessment report."""
|
||||
total_issues = 0
|
||||
critical_issues = []
|
||||
|
||||
for audit in kms_audits:
|
||||
total_issues += len(audit.get("issues", []))
|
||||
if not audit.get("has_attestation_condition"):
|
||||
critical_issues.append(f"KMS key {audit['key_id']} has no attestation conditions")
|
||||
|
||||
for audit in iam_audits:
|
||||
total_issues += len(audit.get("issues", []))
|
||||
if audit.get("overprivileged"):
|
||||
critical_issues.append(f"IAM role {audit['role_name']} is overprivileged")
|
||||
|
||||
report = {
|
||||
"report_type": "Nitro Enclave Security Assessment",
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"summary": {
|
||||
"enclave_instances": len(instances),
|
||||
"kms_keys_audited": len(kms_audits),
|
||||
"iam_roles_audited": len(iam_audits),
|
||||
"cloudtrail_events": len(cloudtrail_events),
|
||||
"total_issues": total_issues,
|
||||
"critical_issues": len(critical_issues),
|
||||
},
|
||||
"critical_findings": critical_issues,
|
||||
"instances": instances,
|
||||
"kms_policy_audits": kms_audits,
|
||||
"iam_role_audits": iam_audits,
|
||||
"cloudtrail_events": cloudtrail_events,
|
||||
}
|
||||
|
||||
if attestation_results:
|
||||
report["attestation_validation"] = attestation_results
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="AWS Nitro Enclave Security Assessment Agent")
|
||||
parser.add_argument("--region", default="us-east-1", help="AWS region")
|
||||
parser.add_argument("--kms-key-ids", nargs="+", help="KMS key IDs to audit")
|
||||
parser.add_argument("--iam-roles", nargs="+", help="IAM role names to audit for enclave permissions")
|
||||
parser.add_argument("--attestation-doc", help="Base64-encoded attestation document to validate")
|
||||
parser.add_argument("--cloudtrail-days", type=int, default=7, help="Days of CloudTrail history to search")
|
||||
parser.add_argument("--output", default="nitro_enclave_security_report.json", help="Output report file")
|
||||
args = parser.parse_args()
|
||||
|
||||
session = boto3.Session(region_name=args.region)
|
||||
ec2_client = session.client("ec2")
|
||||
kms_client = session.client("kms")
|
||||
iam_client = session.client("iam")
|
||||
cloudtrail_client = session.client("cloudtrail")
|
||||
|
||||
logger.info("Starting Nitro Enclave security assessment in %s", args.region)
|
||||
|
||||
# Step 1: Find enclave-enabled instances
|
||||
instances = get_nitro_instances(ec2_client, args.region)
|
||||
|
||||
# Step 2: Audit KMS key policies
|
||||
kms_audits = []
|
||||
if args.kms_key_ids:
|
||||
for key_id in args.kms_key_ids:
|
||||
logger.info("Auditing KMS key: %s", key_id)
|
||||
kms_audits.append(audit_kms_key_policy(kms_client, key_id))
|
||||
else:
|
||||
# Auto-discover KMS keys
|
||||
try:
|
||||
keys_response = kms_client.list_keys(Limit=100)
|
||||
for key in keys_response.get("Keys", []):
|
||||
audit = audit_kms_key_policy(kms_client, key["KeyId"])
|
||||
if audit.get("has_attestation_condition") or audit.get("allowed_actions"):
|
||||
kms_audits.append(audit)
|
||||
except ClientError as e:
|
||||
logger.warning("Cannot list KMS keys: %s", e)
|
||||
|
||||
# Step 3: Audit IAM roles
|
||||
iam_audits = []
|
||||
if args.iam_roles:
|
||||
for role_name in args.iam_roles:
|
||||
logger.info("Auditing IAM role: %s", role_name)
|
||||
iam_audits.append(audit_iam_role_for_enclave(iam_client, role_name))
|
||||
|
||||
# Step 4: Search CloudTrail events
|
||||
cloudtrail_events = audit_cloudtrail_enclave_events(cloudtrail_client, args.cloudtrail_days)
|
||||
|
||||
# Step 5: Validate attestation document if provided
|
||||
attestation_results = None
|
||||
if args.attestation_doc:
|
||||
logger.info("Validating attestation document")
|
||||
attestation_results = validate_attestation_document_structure(args.attestation_doc)
|
||||
|
||||
# Generate report
|
||||
report = generate_report(instances, kms_audits, iam_audits, cloudtrail_events, attestation_results)
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
logger.info("Report saved to %s", args.output)
|
||||
|
||||
# Print summary
|
||||
summary = report["summary"]
|
||||
logger.info(
|
||||
"Assessment complete: %d instances, %d KMS keys, %d IAM roles, %d issues (%d critical)",
|
||||
summary["enclave_instances"],
|
||||
summary["kms_keys_audited"],
|
||||
summary["iam_roles_audited"],
|
||||
summary["total_issues"],
|
||||
summary["critical_issues"],
|
||||
)
|
||||
|
||||
if report["critical_findings"]:
|
||||
logger.warning("CRITICAL FINDINGS:")
|
||||
for finding in report["critical_findings"]:
|
||||
logger.warning(" - %s", finding)
|
||||
|
||||
return 0 if summary["critical_issues"] == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user