Continuous External Attack Surface Discovery & Vulnerability Scanning Across AWS Organizations
Enumerate every public-facing IP across all AWS accounts in your Organization, scan with nmap + Vulners, and alert on new open ports via Slack. Built entirely in Python CDK — deploy with a single cdk deploy.
You can't protect what you can't see. AWS makes it trivially easy to expose resources — a public RDS instance, an overlooked Elastic IP, a Lightsail box someone forgot about. AWS Inspector only covers EC2. Commercial ASM tools (Censys, Shodan, Mandiant Advantage) cost $20K+/year.
This project gives you Organization-wide external asset discovery and vulnerability scanning for the cost of a few Lambda invocations and a NAT Gateway.
Three Lambda functions form a fan-out pipeline:
| Lambda | Runtime | Role | What It Does |
|---|---|---|---|
asm-1 |
Python 3.12 | Orchestrator | Assumes into Org account, lists all AWS accounts, invokes asm-2 per account (async) |
asm-2 |
Python 3.12 | Enumerator | Assumes spoke role in target account, enumerates 10 services for public IPs, invokes asm-3 per IP (async) |
asm-3 |
Docker (nmap) | Scanner | Runs nmap --script vuln against each IP, deduplicates via DynamoDB, alerts Slack |
EC2 (instances + Elastic IPs), Classic ELB, ALB/NLB, Elastic Beanstalk, API Gateway, RDS, Redshift, CloudFront, Lightsail (instances + load balancers), OpenSearch/Elasticsearch
If you have 100 AWS accounts and each averages 5 public IPs:
- asm-1 runs once → invokes asm-2 100 times
- Each asm-2 invokes asm-3 per IP → ~500 nmap scans running in parallel
- Total wall-clock time: ~15 minutes for an entire Organization
- FleetAccess deployed (hub-001, spoke-001 roles)
- CDK bootstrapped in the Security account
npm install -g aws-cdk
git clone https://github.com/raajheshkannaa/asm
cd asm
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtEdit stacks/config.py with your account IDs:
HUB_ACCOUNT = '123456789012' # Security/Automation account
ORG_ACCOUNT = '987654321098' # Organization management accountOr set as environment variables: HUB_ACCOUNT, ORG_ACCOUNT.
Set SLACK_HOOK_URL in the asm-3 Lambda environment (via stacks/asm_stack.py or AWS console).
cdk ls
export AWS_PROFILE=security-account
cdk deployFirst deployment creates the CodePipeline. Push code to the CodeCommit repo it creates and the pipeline self-mutates from there.
| Decision | Rationale |
|---|---|
| Three Lambdas instead of one | Fan-out parallelism — 500 accounts × 10 services would timeout a single Lambda |
| Docker Lambda for nmap | nmap requires native binaries; Docker image bundles nmap + Vulners scripts |
| DynamoDB deduplication | Prevents duplicate Slack alerts on repeat scans for the same IP:port |
| VPC with NAT Gateway | Lambdas need outbound internet for nmap scans but no inbound exposure |
ipaddress module for RFC 1918 |
Correct private IP detection (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x) |
Async invocation (Event type) |
Each Lambda fires and forgets — no waiting, maximum parallelism |
| No broker role hop | Simplified from original; hub-001 assumes directly into org-read-only and spoke roles |
| Component | Monthly Cost (100 accounts, daily scan) |
|---|---|
| Lambda (asm-1 + asm-2) | ~$2 |
| Lambda (asm-3, 500 IPs × 30 days) | ~$15 |
| NAT Gateway | ~$35 |
| DynamoDB (on-demand) | ~$1 |
| Total | ~$53/month |
- fleet-access — Hub & Spoke IAM roles (required foundation)
- aws-cloudtrail-lake-detections — CloudTrail Lake detection engineering
- green-stone — Security group change detection & ChatOps revert
MIT
