Reconstruct point-in-time AWS infrastructure snapshots by replaying CloudTrail events.
Given any historical timestamp, CloudNecromancer resurrects every resource that existed at that moment — EC2 instances, IAM roles, S3 buckets, Lambda functions, security groups, VPCs, RDS databases, and more — from create/modify/delete event chains stored in CloudTrail.
░░░░░░░░░░░░░░░░░░░░░░░░░░
░ ☠ CloudNecromancer ☠ ░
░░░░░░░░░░░░░░░░░░░░░░░░░░
Raising the dead since 2026
- Incident response — "What was running at 3am before the breach?"
- Compliance audits — Point-in-time inventory for any past date
- Post-incident timelines — Full infrastructure state reconstruction
- Drift analysis — Compare two timestamps to see what changed
# Homebrew (macOS / Linux)
brew tap pfrederiksen/tap
brew install cloudnecromancer
# From source (requires CGO + DuckDB headers)
go install github.com/pfrederiksen/cloudnecromancer@latestPre-built binaries for macOS (Intel/Apple Silicon) and Linux (amd64) are available on the Releases page.
# 1. Fetch CloudTrail events into a local database
cloudnecromancer fetch \
--account-id 123456789012 \
--regions us-east-1,us-west-2 \
--start 2026-01-01T00:00:00Z \
--end 2026-03-01T00:00:00Z
# 2. Resurrect infrastructure at a specific point in time
cloudnecromancer resurrect --at 2026-02-15T03:00:00Z
# 3. Compare two points in time
cloudnecromancer diff \
--from 2026-01-01T00:00:00Z \
--to 2026-02-15T03:00:00Z
# 4. Export as Terraform HCL
cloudnecromancer resurrect --at 2026-02-15T03:00:00Z --format terraform --output snapshot.tfQueries the LookupEvents API directly. Simple to set up, but limited to the last 90 days.
cloudnecromancer fetch \
--source cloudtrail \
--account-id 123456789012 \
--regions us-east-1,us-west-2 \
--start 2026-01-01T00:00:00Z \
--end 2026-03-01T00:00:00Z \
[--profile my-aws-profile]Requires read-only CloudTrail permissions. See AWS_PERMISSIONS.md for the minimal IAM policy.
Queries CloudTrail events from a Splunk index. No time limit — go as far back as your retention allows.
cloudnecromancer fetch \
--source splunk \
--splunk-url https://splunk.corp:8089 \
--splunk-token $SPLUNK_TOKEN \
--account-id 123456789012 \
--regions us-east-1,us-west-2 \
--start 2024-01-01T00:00:00Z \
--end 2026-03-01T00:00:00Z| Flag | Default | Description |
|---|---|---|
--splunk-url |
(or SPLUNK_URL env) |
Splunk REST API base URL (e.g. https://splunk.corp:8089) |
--splunk-token |
(or SPLUNK_TOKEN env) |
Splunk bearer token |
--splunk-index |
aws_cloudtrail |
Splunk index containing CloudTrail events |
--splunk-sourcetype |
aws:cloudtrail |
Splunk sourcetype |
--splunk-query |
(auto-generated) | Override the generated SPL query entirely |
--splunk-insecure |
false |
Skip TLS certificate verification |
The auto-generated SPL query searches the configured index/sourcetype and filters by account ID and regions. Use --splunk-query to provide your own SPL if your CloudTrail data uses a non-standard schema.
| Command | Description |
|---|---|
fetch |
Pull CloudTrail events from CloudTrail API or Splunk into local DuckDB |
resurrect |
Reconstruct infrastructure state at a point in time |
diff |
Compare infrastructure between two timestamps |
export |
Re-export an existing snapshot in a different format |
info |
Show database statistics |
cloudnecromancer resurrect \
--at 2026-02-15T03:00:00Z \
[--services ec2,iam,s3] \
[--region us-east-1] \
[--format json|terraform|cloudformation|cdk|pulumi|ocsf|csv] \
[--output ./snapshot.json] \
[--include-dead] \
[--ritual]cloudnecromancer diff \
--from 2026-01-01T00:00:00Z \
--to 2026-02-15T03:00:00Z \
[--format table|json]cloudnecromancer export \
--input ./snapshot.json \
--format hcl \
--output ./snapshot.tf| Format | Flag | Description |
|---|---|---|
| JSON | --format json |
Full snapshot with nested resource attributes |
| Terraform | --format terraform |
HCL with import + resource blocks (aliases: hcl, tf) |
| CloudFormation | --format cloudformation |
CloudFormation JSON template (alias: cfn) |
| CDK | --format cdk |
AWS CDK TypeScript stack using L1 constructs |
| Pulumi | --format pulumi |
Pulumi TypeScript program using @pulumi/aws |
| OCSF | --format ocsf |
OCSF Inventory Info events (class_uid 5001), NDJSON |
| CSV | --format csv |
Splunk lookup table format |
Output format examples
JSON (--format json):
{
"timestamp": "2026-02-15T03:00:00Z",
"account_id": "123456789012",
"resources": {
"ec2:instance": [
{
"resource_id": "i-0abc123def456789",
"state": "running",
"attributes": {
"instance_type": "t3.medium",
"image_id": "ami-0abcdef1234567890"
}
}
]
}
}Terraform HCL (--format terraform):
import {
to = aws_instance.i_0abc123def456789
id = "i-0abc123def456789"
}
resource "aws_instance" "i_0abc123def456789" {
instance_type = "t3.medium"
ami = "ami-0abcdef1234567890"
subnet_id = "subnet-0123456789abcdef0"
}CDK (--format cdk):
new ec2.CfnInstance(this, "i-0abc123def456789", {
instanceType: "t3.medium",
imageId: "ami-0abcdef1234567890",
subnetId: "subnet-0123456789abcdef0",
});Pulumi (--format pulumi):
const i_0abc123def456789 = new aws.ec2.Instance("i-0abc123def456789", {
instanceType: "t3.medium",
ami: "ami-0abcdef1234567890",
subnetId: "subnet-0123456789abcdef0",
});CSV (--format csv) — for use as a Splunk lookup table:
| inputlookup cloudnecromancer_lookup.csv
| join resource_id [search index=cloudtrail earliest=-30d]
| Flag | Default | Description |
|---|---|---|
--db |
./necromancer.db |
Path to DuckDB database file |
--profile |
(default chain) | AWS profile to use |
--quiet |
false |
Suppress banner and non-essential output |
--verbose |
false |
Enable verbose logging |
| Service | Resources |
|---|---|
| EC2 | instances, VPCs, subnets, security groups, IGWs |
| IAM | roles, users, policies |
| S3 | buckets, policies, versioning, public access |
| Lambda | functions |
| RDS | instances, clusters |
| ELB | load balancers, target groups, listeners |
| ECS | clusters, services, task definitions |
| EKS | clusters, nodegroups |
| KMS | keys, aliases, rotation |
| Secrets Manager | secrets, rotation, restore |
| CloudWatch Logs | log groups, log streams, retention |
| DynamoDB | tables, global tables |
| SNS | topics, subscriptions |
| SQS | queues, queue attributes |
| API Gateway | REST APIs, HTTP APIs, stages |
| Route 53 | hosted zones, record sets |
| ECR | repositories, lifecycle policies, scanning |
| ElastiCache | clusters, replication groups |
| WAF v2 | web ACLs, rule groups, IP sets |
| GuardDuty | detectors, filters |
| CloudFront | distributions, origin access controls |
| EBS | volumes, snapshots |
| SSM | documents, parameters, maintenance windows |
All services support create, update, and delete event tracking (133 CloudTrail events total).
-
Fetch — Pull CloudTrail events from the CloudTrail API or Splunk and store them in an embedded DuckDB database. Multi-region fetches run concurrently.
-
Parse — Each event is routed through a service-specific parser that extracts a
ResourceDelta: the action (create/update/delete), resource ID, and relevant attributes. -
Replay — To reconstruct state at time T, the engine queries all events before T (ordered chronologically) and applies each delta to an in-memory resource map.
-
Export — The final snapshot is serialized in the requested format.
make build # Build binary to ./bin/cloudnecromancer
make test # Run all tests
make lint # Run golangci-lint- Create
internal/parser/services/myservice.go - Implement the
Parserinterface - Register in
init()withparser.Register(&MyServiceParser{}) - Add test fixtures to
testdata/ - Add table-driven tests in
internal/parser/services/myservice_test.go
- Create
internal/export/myformat.go - Implement the
Exporterinterface (Export(snapshot, writer) error) - Register it in
GetExporter()ininternal/export/exporter.go - Add tests in
internal/export/export_test.go
MIT