Setup guide
Everything you must provision before this skill can run โ the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.
An AWS account with AWS CLI v2 installed and credentials resolving to a principal that can at minimum read EC2 networking, ELB, Route 53, and CloudWatch Logs in the target region.
You need to already have
- An AWS account you are authorized to inspect and, for write operations, to modify
- AWS CLI v2 installed (aws --version reports aws-cli/2.x)
- Permission to create or be assigned an IAM role/user with VPC read access (arn:aws:iam::aws:policy/AmazonVPCReadOnlyAccess or broader)
- IAM Identity Center (SSO) configured, or a long-lived access key pair, for the target account
- jq installed if you intend to run the bash reference implementation
- python 3.9+ with boto3 installed (pip install boto3) for the scripted path
How the pieces connect
Credentials this skill needs
Set these as environment variables. Never paste secrets into a chat or commit them.
| Variable | What it is | Where to get it |
|---|---|---|
AWS_PROFILE | AWS named profile format: Profile name matching a [profile <name>] section in ~/.aws/config, e.g. prod-network | Create with `aws configure sso --profile prod-network` (IAM Identity Center, preferred) or `aws configure --profile prod-network`. Verify the profile resolves with `aws configure list --profile prod-network`. |
AWS_REGION | AWS region format: Region code such as us-east-1, eu-west-1, ap-southeast-2 | Set per shell with `export AWS_REGION=us-east-1`, or persist with `aws configure set region us-east-1 --profile prod-network`. VPCs are regional, so this must match the region containing the VPC. |
AWS_ACCESS_KEY_IDsecretoptional | AWS access key id format: 20-character string beginning with AKIA (IAM user) or ASIA (temporary STS credentials) | AWS Console > IAM > Users > <user> > Security credentials > Access keys > Create access key. Only needed when SSO/AWS_PROFILE is not available; prefer short-lived SSO or role credentials. |
AWS_SECRET_ACCESS_KEYsecretoptional | AWS secret access key format: 40-character base64-like string, shown exactly once at key creation | Displayed alongside the access key id at IAM > Users > <user> > Security credentials > Create access key. Cannot be retrieved later; rotate if lost. |
AWS_SESSION_TOKENsecretoptional | AWS session token format: Long opaque string; required only with ASIA... temporary credentials | Returned by `aws sts assume-role --role-arn arn:aws:iam::<account-id>:role/NetworkReadOnly --role-session-name net-arch` or by AWS Identity Center console 'Command line or programmatic access'. |
Permissions to grant
arn:aws:iam::aws:policy/AmazonVPCReadOnlyAccessIAM policyGrants the ec2:Describe* actions used to inventory VPCs, subnets, route tables, security groups, NACLs, endpoints, peering connections, and Transit Gateway attachments during triage.
โณ For read-only triage this is already close to minimal; a tighter inline policy can list just ec2:DescribeVpcs, ec2:DescribeSubnets, ec2:DescribeRouteTables, ec2:DescribeSecurityGroupRules, ec2:DescribeNetworkAcls, ec2:DescribeNetworkInterfaces, ec2:DescribeVpcEndpoints.
ec2:DescribeSecurityGroupRulesIAM policyReads individual security group rules including the referenced source security group id, which describe-security-groups summarises less precisely.
โณ Cannot be resource-scoped; ec2:Describe* actions only support Resource: "*".
ec2:CreateRouteIAM policyAdds routes to a route table when wiring NAT egress, peering, or Transit Gateway paths. Always exercised with --dry-run first and behind an explicit confirmation gate.
โณ Scope to specific route tables with Resource: arn:aws:ec2:<region>:<account-id>:route-table/rtb-xxxx and add a Condition on aws:RequestTag or ec2:Vpc to pin the VPC.
ec2:AuthorizeSecurityGroupIngressIAM policyOpens a destination port to a source security group id or CIDR when triage proves the ingress rule is the blocker.
โณ Scope to arn:aws:ec2:<region>:<account-id>:security-group/sg-xxxx and deny 0.0.0.0/0 on admin ports with a Condition on ec2:SourceInstanceARN or an SCP.
ec2:CreateNatGatewayIAM policyCreates per-AZ NAT gateways for private-subnet egress. Billed hourly plus per GB, so it is always cost-flagged and confirmation-gated.
โณ Pair with ec2:AllocateAddress and restrict with a Condition on aws:RequestTag/Environment so only tagged non-production subnets are targetable.
ec2:CreateVpcEndpointIAM policyCreates gateway endpoints for S3/DynamoDB and interface (PrivateLink) endpoints so private subnets reach AWS services without NAT.
โณ Constrain with a Condition on ec2:VpceServiceName to an allow-list of service names, and scope Resource to the target VPC arn.
ec2:CreateFlowLogsIAM policyEnables VPC Flow Logs on a VPC, subnet, or ENI so ACCEPT/REJECT records can prove where packets are dropped.
โณ Also needs iam:PassRole for the flow-log delivery role; scope that PassRole to the single delivery role arn with a Condition on iam:PassedToService = vpc-flow-logs.amazonaws.com.
ec2:StartNetworkInsightsAnalysisIAM policyRuns VPC Reachability Analyzer to identify the exact component blocking a path instead of inferring it.
โณ Must be paired with tiros:CreateQuery and tiros:GetQueryAnswer (Resource: "*") or the analysis fails with AccessDeniedException.
elasticloadbalancing:DescribeTargetHealthIAM policyReads ALB/NLB target health and the TargetHealth.Reason code, which distinguishes a security group block from an application failure.
โณ Read-only and cannot be resource-scoped meaningfully; grant arn:aws:iam::aws:policy/ElasticLoadBalancingReadOnly instead of full access.
route53:ChangeResourceRecordSetsIAM policyCreates or updates alias/A records pointing a hostname at a load balancer. Treated as destructive because UPSERT overwrites existing records.
โณ Scope Resource to arn:aws:route53:::hostedzone/<zone-id> and add a Condition on route53:ChangeResourceRecordSetsRecordTypes and route53:ChangeResourceRecordSetsActions to limit to CREATE of A records.
iam:SimulatePrincipalPolicyIAM policyConfirms which mutating network actions the current principal is actually allowed before proposing a change, avoiding half-applied edits.
โณ Scope Resource to the caller's own principal arn so the skill cannot probe other identities.
sts:GetCallerIdentityIAM policyPrints the account id, ARN, and user id that every safety check and report header depends on. Implicitly allowed for all principals but list it so audits see it.
Step by step
- 1
Install AWS CLI v2
macOS: `brew install awscli` or the signed .pkg. Linux: `curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip && unzip awscliv2.zip && sudo ./aws/install`. Windows: `msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi`. Confirm with `aws --version` and require the output to start with `aws-cli/2.`; v1 lacks several networking subcommands used here.
Open in the console / docs โ - 2
Create a network-scoped IAM role or user
Console > IAM > Roles > Create role > Custom trust policy (or Users > Create user for CLI-only access). Attach arn:aws:iam::aws:policy/AmazonVPCReadOnlyAccess plus arn:aws:iam::aws:policy/ElasticLoadBalancingReadOnly and arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess. Add write actions (ec2:CreateRoute, ec2:AuthorizeSecurityGroupIngress, ec2:CreateNatGateway, ec2:CreateVpcEndpoint, ec2:CreateFlowLogs) only for the accounts where changes are authorized.
Open in the console / docs โ - 3
Configure credentials with IAM Identity Center (preferred)
Run `aws configure sso --profile prod-network`, supply the SSO start URL and region, pick the account and permission set, then `aws sso login --profile prod-network`. This yields short-lived ASIA... credentials with no long-lived secret on disk.
Open in the console / docs โ - 4
Or configure a static key pair (fallback)
Console > IAM > Users > <user> > Security credentials > Access keys > Create access key > Command Line Interface. Then `aws configure --profile prod-network` and paste the AKIA... id, the secret, the region, and `json` as the output format. Rotate these keys on a schedule; prefer the SSO path above.
Open in the console / docs โ - 5
Pin the region and verify identity
`export AWS_PROFILE=prod-network` and `export AWS_REGION=us-east-1`, then run `aws sts get-caller-identity`. Record the returned Account value; the skill compares it against the user-stated target account before every mutation.
Open in the console / docs โ - 6
Smoke-test networking read access
`aws ec2 describe-vpcs --query 'Vpcs[].{Id:VpcId,Cidr:CidrBlock,Default:IsDefault}' --output table` and `aws ec2 describe-route-tables --max-items 5`. If either returns UnauthorizedOperation, the policy attachment in step 2 has not propagated or is missing.
Open in the console / docs โ - 7
Probe write permission without changing anything
`aws ec2 create-tags --resources <a-subnet-id> --tags Key=probe,Value=1 --dry-run`. `DryRunOperation` means writes are available; `UnauthorizedOperation` means the profile is read-only. Confirm with `aws iam simulate-principal-policy --policy-source-arn $(aws sts get-caller-identity --query Arn --output text) --action-names ec2:CreateRoute ec2:AuthorizeSecurityGroupIngress`.
Open in the console / docs โ - 8
Enable VPC Flow Logs if packet-level evidence will be needed
Create a log group (`aws logs create-log-group --log-group-name /aws/vpc/flowlogs`) and a delivery role, then `aws ec2 create-flow-logs --resource-type VPC --resource-ids vpc-0123456789abcdef0 --traffic-type ALL --log-destination-type cloud-watch-logs --log-group-name /aws/vpc/flowlogs --deliver-logs-permission-arn arn:aws:iam::<account-id>:role/flowlogsRole`. Flow logs bill on ingestion; enable per-ENI or per-subnet if cost is a concern.
Open in the console / docs โ - 9
Grant Reachability Analyzer the tiros actions
Add an inline policy with Effect Allow, Action ["tiros:CreateQuery", "tiros:GetQueryAnswer", "tiros:GetQueryExplanation"], Resource "*" alongside the ec2:*NetworkInsights* actions. Without it, StartNetworkInsightsAnalysis is accepted but the analysis returns AccessDeniedException.
Open in the console / docs โ
Check it worked
aws sts get-caller-identity && aws ec2 describe-vpcs --query 'Vpcs[].{Id:VpcId,Cidr:CidrBlock}' --output tableIf you hit an error
UnauthorizedOperation: You are not authorized to perform this operation.Cause: The calling principal lacks the specific ec2 action, or an SCP/permissions boundary denies it. Returned by --dry-run probes as well as real calls.
Fix: Run `aws iam simulate-principal-policy --policy-source-arn $(aws sts get-caller-identity --query Arn --output text) --action-names <action>` to confirm, then attach the missing action. Newer responses include an encoded authorization message decodable with `aws sts decode-authorization-message --encoded-message <blob>`.
DryRunOperation: Request would have succeeded, but DryRun flag is set.Cause: Not a failure. This is the expected success signal from a --dry-run mutating call.
Fix: Treat as authorization confirmed, present the confirmation gate, then re-run without --dry-run.
ExpiredToken: The security token included in the request is expiredCause: Temporary STS or IAM Identity Center credentials passed their expiry (commonly 1-12 hours).
Fix: Re-authenticate with `aws sso login --profile <profile>` or re-run `aws sts assume-role`. Confirm with `aws sts get-caller-identity` before continuing.
InvalidClientTokenId: The security token included in the request is invalid.Cause: AWS_ACCESS_KEY_ID is wrong, deleted, or belongs to a different partition; or stale AWS_SESSION_TOKEN is set alongside a long-lived AKIA key.
Fix: `unset AWS_SESSION_TOKEN` when using a static AKIA key pair, or regenerate the key. Verify with `aws configure list` that the credential source is what you expect.
InvalidSubnet.Conflict: The CIDR '10.0.1.0/24' conflicts with another subnetCause: The requested subnet CIDR overlaps an existing subnet in the same VPC.
Fix: List used ranges with `aws ec2 describe-subnets --filters Name=vpc-id,Values=<vpc> --query 'Subnets[].CidrBlock'` and pick a non-overlapping block, or add a secondary VPC CIDR with `aws ec2 associate-vpc-cidr-block`.
RouteAlreadyExists: The route identified by 0.0.0.0/0 already exists.Cause: create-route was called for a destination CIDR the route table already covers exactly.
Fix: Use `aws ec2 replace-route` to retarget it โ but treat replace-route as destructive and gate it behind explicit confirmation after snapshotting the current route.
DependencyViolation: resource sg-0abc has a dependent objectCause: The security group, subnet, IGW, or VPC is still attached to an ENI or referenced by another security group rule.
Fix: Find holders with `aws ec2 describe-network-interfaces --filters Name=group-id,Values=sg-0abc` and `aws ec2 describe-security-groups --filters Name=ip-permission.group-id,Values=sg-0abc`, then detach or re-point them first.
InvalidPermission.Duplicate: the specified rule already existsCause: authorize-security-group-ingress was issued for a rule already present, often from a retried or concurrently applied change.
Fix: Treat as idempotent success. Verify with `aws ec2 describe-security-group-rules --filters Name=group-id,Values=<sg>` rather than retrying.
AddressLimitExceeded: The maximum number of addresses has been reached.Cause: The account hit the per-region Elastic IP quota (5 by default) while allocating an EIP for a NAT gateway.
Fix: Release unassociated EIPs (`aws ec2 describe-addresses --filters Name=association-id,Values=` then `aws ec2 release-address`) or raise the quota via Service Quotas code L-0263D0A3.
InvalidVpcPeeringConnectionID.NotFound / IncorrectState: pcx-0abc is not in the correct stateCause: The peering request is still `pending-acceptance`, was rejected, or routes are being added before the accepter side accepted it.
Fix: `aws ec2 describe-vpc-peering-connections --vpc-peering-connection-ids pcx-0abc --query 'VpcPeeringConnections[].Status'`, accept from the peer account with `aws ec2 accept-vpc-peering-connection`, then add routes on BOTH sides.
InvalidChangeBatch: RRSet with DNS name api.example.com. is not permitted in zone example.net.Cause: route53 change-resource-record-sets targeted a hosted zone that does not own the record name, or an alias target hosted zone id is wrong.
Fix: Resolve the correct zone with `aws route53 list-hosted-zones-by-name --dns-name example.com`, and for ALB aliases use the load balancer's CanonicalHostedZoneId from `aws elbv2 describe-load-balancers`.
AccessDeniedException when calling StartNetworkInsightsAnalysisCause: The principal has the ec2:*NetworkInsights* actions but is missing tiros:CreateQuery / tiros:GetQueryAnswer, which Reachability Analyzer calls on your behalf.
Fix: Attach an inline policy allowing tiros:CreateQuery, tiros:GetQueryAnswer, tiros:GetQueryExplanation on Resource "*" and retry.
Official documentation: https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html โ
API operations this skill uses (20)
Every call the skill makes, linked to its official reference page.
| Operation | Call | Permission | Ref |
|---|---|---|---|
| List VPCs and their CIDR blocks Establishes the address space and detects overlapping CIDRs before peering or Transit Gateway design. | CLI aws ec2 describe-vpcs --query 'Vpcs[].{Id:VpcId,Cidr:CidrBlock,Extra:CidrBlockAssociationSet[].CidrBlock,Default:IsDefault}' --output table | ec2:DescribeVpcs | docs โ |
| Enumerate subnets with AZ and free-IP counts Maps the tier layout and catches exhausted subnets that cause ENI-allocation failures for ALBs, Lambda, and EKS. | CLI aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-0123456789abcdef0 --query 'Subnets[].{Id:SubnetId,Az:AvailabilityZone,Cidr:CidrBlock,Free:AvailableIpAddressCount,AutoPublicIp:MapPublicIpOnLaunch}' --output table | ec2:DescribeSubnets | docs โ |
| Resolve the effective route table for a subnet Determines whether a subnet is public, private-egress, or isolated, and finds the longest-prefix match for a destination. | CLI aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=subnet-0abc123 --query 'RouteTables[].{Rtb:RouteTableId,Routes:Routes[].{Dest:DestinationCidrBlock,Pl:DestinationPrefixListId,Target:GatewayId||NatGatewayId||TransitGatewayId||VpcPeeringConnectionId,State:State}}' | ec2:DescribeRouteTables | docs โ |
| Find the VPC main route table (unassociated subnets) Subnets without an explicit association silently inherit the main table; skipping this causes the most common misdiagnosis. | CLI aws ec2 describe-route-tables --filters Name=vpc-id,Values=vpc-0123456789abcdef0 Name=association.main,Values=true --query 'RouteTables[0].RouteTableId' --output text | ec2:DescribeRouteTables | docs โ |
| Check internet gateway attachment Confirms a public tier actually has an attached IGW rather than just a route pointing at a detached one. | CLI aws ec2 describe-internet-gateways --filters Name=attachment.vpc-id,Values=vpc-0123456789abcdef0 --query 'InternetGateways[].{Igw:InternetGatewayId,State:Attachments[0].State}' | ec2:DescribeInternetGateways | docs โ |
| Audit NAT gateway placement and state Verifies each NAT gateway sits in a public subnet, is in state available, and that there is one per AZ rather than a shared single point of failure. | CLI aws ec2 describe-nat-gateways --filter Name=vpc-id,Values=vpc-0123456789abcdef0 --query 'NatGateways[].{Nat:NatGatewayId,Subnet:SubnetId,State:State,Eip:NatGatewayAddresses[0].PublicIp,Type:ConnectivityType}' | ec2:DescribeNatGateways | docs โ |
| Read individual security group rules including SG references Shows the exact allowed port ranges and whether the source is a CIDR, a prefix list, or another security group, which describe-security-groups renders less clearly. | CLI aws ec2 describe-security-group-rules --filters Name=group-id,Values=sg-0abc123 --query 'SecurityGroupRules[].{Id:SecurityGroupRuleId,Egress:IsEgress,Proto:IpProtocol,From:FromPort,To:ToPort,Cidr:CidrIpv4,SrcSg:ReferencedGroupInfo.GroupId,Pl:PrefixListId}' --output table | ec2:DescribeSecurityGroupRules | docs โ |
| Inspect network ACL entries on a subnet NACLs are stateless, so both the forward rule and the ephemeral-port return rule must exist; this surfaces one-way paths. | CLI aws ec2 describe-network-acls --filters Name=association.subnet-id,Values=subnet-0abc123 --query 'NetworkAcls[].Entries[].{Rule:RuleNumber,Egress:Egress,Proto:Protocol,Cidr:CidrBlock,Action:RuleAction,Ports:PortRange}' --output table | ec2:DescribeNetworkAcls | docs โ |
| Locate the ENI that owns a private IP Anchors a raw IP from a log or error message to a concrete subnet and security group set so the path walk can start. | CLI aws ec2 describe-network-interfaces --filters Name=addresses.private-ip-address,Values=10.0.1.25 --query 'NetworkInterfaces[].{Eni:NetworkInterfaceId,Subnet:SubnetId,Vpc:VpcId,Sgs:Groups[].GroupId,Desc:Description}' | ec2:DescribeNetworkInterfaces | docs โ |
| List VPC endpoints and private DNS status Detects interface endpoints with private DNS off (traffic silently leaves via NAT) and gateway endpoints missing route table associations. | CLI aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=vpc-0123456789abcdef0 --query 'VpcEndpoints[].{Id:VpcEndpointId,Service:ServiceName,Type:VpcEndpointType,State:State,PrivDns:PrivateDnsEnabled,Subnets:SubnetIds,Sgs:Groups[].GroupId,Rtbs:RouteTableIds}' | ec2:DescribeVpcEndpoints | docs โ |
| Resolve a managed prefix list id for gateway endpoint routes Gateway endpoints for S3 and DynamoDB route via a pl-* prefix list, which must be added to every route table that needs private service access. | CLI aws ec2 describe-prefix-lists --filters Name=prefix-list-name,Values=com.amazonaws.us-east-1.s3 --query 'PrefixLists[].{Id:PrefixListId,Name:PrefixListName}' | ec2:DescribePrefixLists | docs โ |
| Verify VPC peering status and both-sided routing Confirms the connection is active (not pending-acceptance) and exposes both CIDRs so you can check for a route on each side. | CLI aws ec2 describe-vpc-peering-connections --filters Name=status-code,Values=active --query 'VpcPeeringConnections[].{Pcx:VpcPeeringConnectionId,Requester:RequesterVpcInfo.VpcId,RequesterCidr:RequesterVpcInfo.CidrBlock,Accepter:AccepterVpcInfo.VpcId,AccepterCidr:AccepterVpcInfo.CidrBlock,Status:Status.Code}' | ec2:DescribeVpcPeeringConnections | docs โ |
| Search Transit Gateway routes for a destination Proves whether the TGW route table actually has a propagated or static route to the destination VPC, the usual cause of TGW black holes. | CLI aws ec2 search-transit-gateway-routes --transit-gateway-route-table-id tgw-rtb-0abc123 --filters Name=route-search.subnet-of-match,Values=10.20.0.0/16 --query 'Routes[].{Cidr:DestinationCidrBlock,Type:Type,State:State,Attachment:TransitGatewayAttachments[0].TransitGatewayAttachmentId}' | ec2:SearchTransitGatewayRoutes | docs โ |
| List Transit Gateway attachments and their association state An attachment without a route table association, or without propagation into the peer table, is attached but unreachable. | CLI aws ec2 describe-transit-gateway-attachments --filters Name=transit-gateway-id,Values=tgw-0abc123 --query 'TransitGatewayAttachments[].{Att:TransitGatewayAttachmentId,Type:ResourceType,Resource:ResourceId,State:State,RtbAssoc:Association.TransitGatewayRouteTableId}' | ec2:DescribeTransitGatewayAttachments | docs โ |
| Create a Reachability Analyzer path Defines the source, destination, protocol, and port to be analysed without sending any real packets. | CLI aws ec2 create-network-insights-path --source i-0aaa111bbb222ccc3 --destination i-0ddd444eee555fff6 --protocol tcp --destination-port 5432 --query NetworkInsightsPath.NetworkInsightsPathId --output text | ec2:CreateNetworkInsightsPath | docs โ |
| Run the reachability analysis and read the blocking component Returns the exact component (security group, NACL, route table, missing IGW) that blocks the path, replacing guesswork. Billed per analysis. | CLI aws ec2 start-network-insights-analysis --network-insights-path-id nip-0abc123 --query NetworkInsightsAnalysis.NetworkInsightsAnalysisId --output text && aws ec2 describe-network-insights-analyses --network-insights-analysis-ids nia-0abc123 --query 'NetworkInsightsAnalyses[0].{Status:Status,Reachable:NetworkPathFound,Codes:Explanations[].ExplanationCode}' | ec2:StartNetworkInsightsAnalysis + ec2:DescribeNetworkInsightsAnalyses + tiros:CreateQuery + tiros:GetQueryAnswer | docs โ |
| Query VPC Flow Logs for REJECTs on a 5-tuple Gives packet-level ACCEPT/REJECT evidence; a REJECT points at SG/NACL, and no record at all points at routing. | CLI aws logs start-query --log-group-name /aws/vpc/flowlogs --start-time $(($(date +%s) - 3600)) --end-time $(date +%s) --query-string 'fields @timestamp, srcAddr, dstAddr, dstPort, action | filter dstAddr = "10.0.2.40" and dstPort = 5432 | sort @timestamp desc | limit 50' | logs:StartQuery (then logs:GetQueryResults with the returned queryId) | docs โ |
| Read load balancer target health and failure reason Target.Timeout means the target security group blocks the load balancer; Target.ResponseCodeMismatch means the network is fine and the app is at fault. | CLI aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/app-tg/0123456789abcdef --query 'TargetHealthDescriptions[].{Target:Target.Id,Port:Target.Port,State:TargetHealth.State,Reason:TargetHealth.Reason,Desc:TargetHealth.Description}' --output table | elasticloadbalancing:DescribeTargetHealth | docs โ |
| Test what Route 53 will answer for a name Validates routing policies, health-check-driven failover, and alias targets without waiting for DNS TTLs to expire on a client. | CLI aws route53 test-dns-answer --hosted-zone-id Z1D633PJN98FT9 --record-name api.example.com --record-type A --query '{Answer:RecordData,Code:ResponseCode}' | route53:TestDNSAnswer | docs โ |
| Add a route behind a dry-run and confirmation gate The single most common fix in cross-VPC troubleshooting; always dry-run first and pair with the reverse route on the peer side. | CLI aws ec2 create-route --route-table-id rtb-0abc123 --destination-cidr-block 10.20.0.0/16 --transit-gateway-id tgw-0abc123 --dry-run # expect DryRunOperation, gate, then re-run without --dry-run | ec2:CreateRoute | docs โ |