From 3facc5f912eb59409bd7ca589f73f83c134f800a Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 12 Jul 2026 20:49:02 +1000 Subject: [PATCH] Slice 1: golden fixture + account sanitizer (#9) - internal/topology.Sanitize: redact AWS account id (Account, ARNs, SG-peer accounts, ENI instance-owner ids); keep resource ids + RFC1918 IPs - scan --scrub: reproducible sanitized snapshots (shareable / fixtures) - testdata/testenv.golden.json: sanitized snapshot of the testenv (5 ENIs incl. ALB/RDS, alb->app->db SG chain, gateway-endpoint route) Closes #9 --- internal/cli/scan.go | 6 + internal/topology/sanitize.go | 36 ++++ testdata/README.md | 32 +++ testdata/testenv.golden.json | 373 ++++++++++++++++++++++++++++++++++ 4 files changed, 447 insertions(+) create mode 100644 internal/topology/sanitize.go create mode 100644 testdata/README.md create mode 100644 testdata/testenv.golden.json diff --git a/internal/cli/scan.go b/internal/cli/scan.go index 008ef84..4949998 100644 --- a/internal/cli/scan.go +++ b/internal/cli/scan.go @@ -6,6 +6,7 @@ import ( "os" "github.com/nkcoder/reachr/internal/awsscan" + "github.com/nkcoder/reachr/internal/topology" "github.com/spf13/cobra" ) @@ -16,6 +17,7 @@ var ( scanFilter string scanOutput string scanRaw bool + scanScrub bool ) var scanCmd = &cobra.Command{ @@ -47,6 +49,9 @@ func runScan(cmd *cobra.Command) error { return fmt.Errorf("scan backbone: %w", err) } snap := raw.ToSnapshot(account, scanFilter) + if scanScrub { + topology.Sanitize(snap) + } f, err := os.Create(scanOutput) if err != nil { @@ -91,5 +96,6 @@ func init() { f.StringVar(&scanFilter, "filter", "", "scope selector, e.g. tag:project=X") f.StringVarP(&scanOutput, "output", "o", "topology.json", "snapshot output path") f.BoolVar(&scanRaw, "raw", false, "exploratory: dump raw AWS backbone JSON to stdout") + f.BoolVar(&scanScrub, "scrub", false, "redact the AWS account id (for shareable snapshots / fixtures)") _ = scanCmd.MarkFlagRequired("region") } diff --git a/internal/topology/sanitize.go b/internal/topology/sanitize.go new file mode 100644 index 0000000..46c9bc8 --- /dev/null +++ b/internal/topology/sanitize.go @@ -0,0 +1,36 @@ +package topology + +import "strings" + +// RedactedAccount is the placeholder substituted for the real AWS account id. +const RedactedAccount = "000000000000" + +// Sanitize redacts the AWS account id from a snapshot so it can be committed as a +// fixture or shared. It replaces the account everywhere it appears — the Account +// field, ARNs, SG-peer accounts, and ENI instance-owner ids — while keeping resource +// ids and RFC1918 private IPs (not sensitive, and needed for realistic reasoning). +func Sanitize(s *Snapshot) { + account := s.Account + if account == "" || account == RedactedAccount { + return + } + redact := func(v string) string { return strings.ReplaceAll(v, account, RedactedAccount) } + + s.Account = RedactedAccount + for i := range s.Subnets { + s.Subnets[i].ARN = redact(s.Subnets[i].ARN) + } + for i := range s.SecurityGroups { + s.SecurityGroups[i].ARN = redact(s.SecurityGroups[i].ARN) + for j := range s.SecurityGroups[i].Rules { + if p := &s.SecurityGroups[i].Rules[j].Peer; p.Account == account { + p.Account = RedactedAccount + } + } + } + for i := range s.ENIs { + if a := s.ENIs[i].Attachment; a != nil && a.InstanceOwnerID == account { + a.InstanceOwnerID = RedactedAccount + } + } +} diff --git a/testdata/README.md b/testdata/README.md new file mode 100644 index 0000000..30b1617 --- /dev/null +++ b/testdata/README.md @@ -0,0 +1,32 @@ +# testdata + +## testenv.golden.json + +A sanitized `topology.json` snapshot of the `testenv/` topology (schemaVersion 1). +This is the golden fixture the reachability engine and renderer are tested against — +no live AWS needed. It exercises the interesting cases: + +| | | +|---|---| +| ENIs | 5 — instance (×2), ALB (×2, `amazon-elb`), RDS (`amazon-rds`) | +| Security groups | 4 — the `alb → app → db` SG-references-SG chain + default self-ref | +| Route tables | 2 — main + custom, with a gateway-endpoint prefix-list route | +| VPC endpoints | 1 — S3 gateway endpoint | + +**Sanitized:** the AWS account id is redacted to `000000000000` (in `account`, ARNs, +SG-peer accounts, and instance-owner ids). Resource ids and RFC1918 private IPs are +kept — they aren't sensitive and are needed for realistic reasoning. Service owners +(`amazon-elb`, `amazon-rds`) are preserved. + +### Regenerating + +Stand up `testenv/` (see `testenv/README.md`) and run a scrubbed scan: + +```sh +go run . scan --region ap-southeast-2 --scrub \ + --vpc "$(terraform -chdir=testenv output -raw vpc_id)" \ + -o testdata/testenv.golden.json +``` + +`--scrub` applies the same redaction (`internal/topology.Sanitize`). `scannedAt` will +differ per scan; pin it if a stable diff matters. diff --git a/testdata/testenv.golden.json b/testdata/testenv.golden.json new file mode 100644 index 0000000..d508e9f --- /dev/null +++ b/testdata/testenv.golden.json @@ -0,0 +1,373 @@ +{ + "schemaVersion": 1, + "scannedAt": "2026-07-12T09:58:23Z", + "region": "ap-southeast-2", + "account": "000000000000", + "toolVersion": "reachr (fixture)", + "vpcs": [ + { + "id": "vpc-0c37291497fa9f1fe", + "cidrBlocks": [ + "10.20.0.0/16" + ], + "isDefault": false, + "state": "available", + "tags": { + "Name": "reachr-testenv", + "managedBy": "terraform", + "project": "reachr-testenv" + } + } + ], + "subnets": [ + { + "id": "subnet-0b7cbca1c0a19d538", + "vpcId": "vpc-0c37291497fa9f1fe", + "cidrBlock": "10.20.1.0/24", + "availabilityZone": "ap-southeast-2b", + "availabilityZoneId": "apse2-az1", + "mapPublicIpOnLaunch": false, + "arn": "arn:aws:ec2:ap-southeast-2:000000000000:subnet/subnet-0b7cbca1c0a19d538", + "tags": { + "Name": "reachr-testenv-private-ap-southeast-2b", + "managedBy": "terraform", + "project": "reachr-testenv" + } + }, + { + "id": "subnet-0067d4e711ecd3e50", + "vpcId": "vpc-0c37291497fa9f1fe", + "cidrBlock": "10.20.0.0/24", + "availabilityZone": "ap-southeast-2a", + "availabilityZoneId": "apse2-az3", + "mapPublicIpOnLaunch": false, + "arn": "arn:aws:ec2:ap-southeast-2:000000000000:subnet/subnet-0067d4e711ecd3e50", + "tags": { + "Name": "reachr-testenv-private-ap-southeast-2a", + "managedBy": "terraform", + "project": "reachr-testenv" + } + } + ], + "routeTables": [ + { + "id": "rtb-00e7787efc430fbd2", + "vpcId": "vpc-0c37291497fa9f1fe", + "main": true, + "subnetIds": null, + "routes": [ + { + "destination": { + "kind": "cidr", + "value": "10.20.0.0/16" + }, + "target": { + "kind": "local", + "id": "local" + }, + "state": "active", + "origin": "CreateRouteTable" + } + ] + }, + { + "id": "rtb-05ef41a62420f0302", + "vpcId": "vpc-0c37291497fa9f1fe", + "main": false, + "subnetIds": [ + "subnet-0067d4e711ecd3e50", + "subnet-0b7cbca1c0a19d538" + ], + "routes": [ + { + "destination": { + "kind": "cidr", + "value": "10.20.0.0/16" + }, + "target": { + "kind": "local", + "id": "local" + }, + "state": "active", + "origin": "CreateRouteTable" + }, + { + "destination": { + "kind": "prefix-list", + "value": "pl-6ca54005" + }, + "target": { + "kind": "vpc-endpoint-gw", + "id": "vpce-0ec1a3bec0d211bda" + }, + "state": "active", + "origin": "CreateRoute" + } + ], + "tags": { + "Name": "reachr-testenv-private", + "managedBy": "terraform", + "project": "reachr-testenv" + } + } + ], + "internetGateways": null, + "natGateways": null, + "enis": [ + { + "id": "eni-01b891bc8e35745b8", + "vpcId": "vpc-0c37291497fa9f1fe", + "subnetId": "subnet-0b7cbca1c0a19d538", + "availabilityZone": "ap-southeast-2b", + "interfaceType": "interface", + "status": "in-use", + "privateIps": [ + "10.20.1.208" + ], + "securityGroupIds": [ + "sg-011cccd596fe3aece" + ], + "attachment": { + "instanceId": "i-0ea49b4de16a5abb8", + "instanceOwnerId": "000000000000", + "requesterManaged": false, + "deviceIndex": 0, + "status": "attached" + } + }, + { + "id": "eni-09f86fceda72f73b9", + "vpcId": "vpc-0c37291497fa9f1fe", + "subnetId": "subnet-0b7cbca1c0a19d538", + "availabilityZone": "ap-southeast-2b", + "description": "ELB app/reachr-testenv-alb/752640ba33773d03", + "interfaceType": "interface", + "status": "in-use", + "privateIps": [ + "10.20.1.93" + ], + "securityGroupIds": [ + "sg-0823865aa2bf98c46" + ], + "attachment": { + "instanceOwnerId": "amazon-elb", + "requesterId": "amazon-elb", + "requesterManaged": true, + "deviceIndex": 1, + "status": "attached" + } + }, + { + "id": "eni-0076a3b5facf1da0e", + "vpcId": "vpc-0c37291497fa9f1fe", + "subnetId": "subnet-0067d4e711ecd3e50", + "availabilityZone": "ap-southeast-2a", + "description": "ELB app/reachr-testenv-alb/752640ba33773d03", + "interfaceType": "interface", + "status": "in-use", + "privateIps": [ + "10.20.0.132" + ], + "securityGroupIds": [ + "sg-0823865aa2bf98c46" + ], + "attachment": { + "instanceOwnerId": "amazon-elb", + "requesterId": "amazon-elb", + "requesterManaged": true, + "deviceIndex": 1, + "status": "attached" + } + }, + { + "id": "eni-05f4100d79348f49d", + "vpcId": "vpc-0c37291497fa9f1fe", + "subnetId": "subnet-0067d4e711ecd3e50", + "availabilityZone": "ap-southeast-2a", + "interfaceType": "interface", + "status": "in-use", + "privateIps": [ + "10.20.0.221" + ], + "securityGroupIds": [ + "sg-011cccd596fe3aece" + ], + "attachment": { + "instanceId": "i-0d0426503a83c0710", + "instanceOwnerId": "000000000000", + "requesterManaged": false, + "deviceIndex": 0, + "status": "attached" + } + }, + { + "id": "eni-04c527823f215a8ad", + "vpcId": "vpc-0c37291497fa9f1fe", + "subnetId": "subnet-0067d4e711ecd3e50", + "availabilityZone": "ap-southeast-2a", + "description": "RDSNetworkInterface", + "interfaceType": "interface", + "status": "in-use", + "privateIps": [ + "10.20.0.45" + ], + "securityGroupIds": [ + "sg-03fc7dfa1919e23a4" + ], + "attachment": { + "instanceOwnerId": "amazon-rds", + "requesterId": "amazon-rds", + "requesterManaged": true, + "deviceIndex": 1, + "status": "attached" + } + } + ], + "securityGroups": [ + { + "id": "sg-0823865aa2bf98c46", + "vpcId": "vpc-0c37291497fa9f1fe", + "name": "reachr-testenv-alb", + "description": "ALB tier", + "arn": "arn:aws:ec2:ap-southeast-2:000000000000:security-group/sg-0823865aa2bf98c46", + "rules": [ + { + "direction": "ingress", + "protocol": "tcp", + "fromPort": 80, + "toPort": 80, + "peer": { + "kind": "cidr", + "value": "10.20.0.0/16" + }, + "description": "HTTP from within VPC" + }, + { + "direction": "egress", + "protocol": "tcp", + "fromPort": 8080, + "toPort": 8080, + "peer": { + "kind": "sg", + "value": "sg-011cccd596fe3aece", + "account": "000000000000" + }, + "description": "To app tier" + } + ], + "tags": { + "Name": "reachr-testenv-alb", + "managedBy": "terraform", + "project": "reachr-testenv" + } + }, + { + "id": "sg-011cccd596fe3aece", + "vpcId": "vpc-0c37291497fa9f1fe", + "name": "reachr-testenv-app", + "description": "App tier (stands in for ECS)", + "arn": "arn:aws:ec2:ap-southeast-2:000000000000:security-group/sg-011cccd596fe3aece", + "rules": [ + { + "direction": "ingress", + "protocol": "tcp", + "fromPort": 8080, + "toPort": 8080, + "peer": { + "kind": "sg", + "value": "sg-0823865aa2bf98c46", + "account": "000000000000" + }, + "description": "From ALB" + }, + { + "direction": "egress", + "protocol": "tcp", + "fromPort": 5432, + "toPort": 5432, + "peer": { + "kind": "sg", + "value": "sg-03fc7dfa1919e23a4", + "account": "000000000000" + }, + "description": "To DB" + } + ], + "tags": { + "Name": "reachr-testenv-app", + "managedBy": "terraform", + "project": "reachr-testenv" + } + }, + { + "id": "sg-03fc7dfa1919e23a4", + "vpcId": "vpc-0c37291497fa9f1fe", + "name": "reachr-testenv-db", + "description": "RDS tier", + "arn": "arn:aws:ec2:ap-southeast-2:000000000000:security-group/sg-03fc7dfa1919e23a4", + "rules": [ + { + "direction": "ingress", + "protocol": "tcp", + "fromPort": 5432, + "toPort": 5432, + "peer": { + "kind": "sg", + "value": "sg-011cccd596fe3aece", + "account": "000000000000" + }, + "description": "Postgres from app" + } + ], + "tags": { + "Name": "reachr-testenv-db", + "managedBy": "terraform", + "project": "reachr-testenv" + } + }, + { + "id": "sg-03782cf05b2b1d7be", + "vpcId": "vpc-0c37291497fa9f1fe", + "name": "default", + "description": "default VPC security group", + "arn": "arn:aws:ec2:ap-southeast-2:000000000000:security-group/sg-03782cf05b2b1d7be", + "rules": [ + { + "direction": "ingress", + "protocol": "all", + "peer": { + "kind": "sg", + "value": "sg-03782cf05b2b1d7be", + "account": "000000000000" + } + }, + { + "direction": "egress", + "protocol": "all", + "peer": { + "kind": "cidr", + "value": "0.0.0.0/0" + } + } + ] + } + ], + "vpcEndpoints": [ + { + "id": "vpce-0ec1a3bec0d211bda", + "vpcId": "vpc-0c37291497fa9f1fe", + "serviceName": "com.amazonaws.ap-southeast-2.s3", + "kind": "gateway", + "state": "available", + "routeTableIds": [ + "rtb-05ef41a62420f0302" + ], + "privateDnsEnabled": false, + "tags": { + "Name": "reachr-testenv-s3", + "managedBy": "terraform", + "project": "reachr-testenv" + } + } + ] +}