AWS
The AWS integration connects Escape ASM to your AWS account through a cross-account IAM role. Escape assumes your role via STS AssumeRole using a shared External ID. No long-lived access keys are stored.
Authentication differs from AWS Account, but ASM data pull is identical: CloudFront, API Gateway, Route 53, Load Balancers, Lambda, and EC2 Elastic IPs.
Discovered Resources¶
The AWS integration automatically discovers and inventories the following services from your AWS account:
- CloudFront Distributions: CDN configurations and domain names serving your applications
- API Gateway Domain Names: Custom domain names configured for API Gateway endpoints
- API Gateway V2 APIs: HTTP and WebSocket APIs (API Gateway v2)
- Route 53 DNS Records: DNS zones and records managed by Route 53
- Load Balancers: Application Load Balancers (ALB) and Network Load Balancers (NLB). Classic Load Balancers aren't discovered
- Lambda Functions: Functions with a Function URL enabled (an AWS Lambda Asset and its URL Host). API Gateway endpoints are covered by the API Gateway discovery above
- EC2 Elastic IPs: Public IPs and domains attached to Elastic IP addresses
These discovered resources are automatically classified as Assets (APIs, DNS records, WebApps) in Escape's ASM, enabling continuous security monitoring and testing.
Create a Cross-Account IAM Role¶
Create an IAM role in your AWS account, then paste its ARN into Escape. The trusted account ID is 178659809426.
Copy the External ID¶
- Open the AWS integration page
- Copy the External ID
Create the Role¶
-
Open the IAM roles page and click Create role
-
Select Custom trust policy
-
Paste the trust policy below, replacing
YOUR_EXTERNAL_IDwith the External ID -
Click Next
-
Attach the ReadOnlyAccess policy, or attach nothing and add the custom policy below
-
Click Next
-
Name the role
EscapeIntegrationReadOnly -
Click Create role
-
Copy the role ARN (
arn:aws:iam::ACCOUNT_ID:role/EscapeIntegrationReadOnly)
Custom Policy¶
Skip this if you attached ReadOnlyAccess.
Adapt this policy to your infrastructure and security requirements. Keep the actions for the AWS resources you want Escape to discover, and account for any service control policies or permissions boundaries in your organization.
- Open the role on the IAM roles page
- Click Add permissions, then Create inline policy
- Open the JSON tab and paste the policy below
- Click Next
- Name the policy
EscapeIntegrationRead - Click Create policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"apigateway:GET",
"cloudfront:ListDistributions",
"ec2:DescribeAddresses",
"elasticloadbalancing:DescribeLoadBalancers",
"lambda:GetFunctionUrlConfig",
"lambda:ListFunctions",
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": "*"
}
]
}
Finish in Escape¶
- Go back to the Escape tab you left open. Don't reopen the form, it generates a new External ID
- Keep the External ID already filled in and paste the role ARN
- Save the integration and wait for validation to complete
The role name is case sensitive and must match AWS.
Multi-Account (AWS Organizations)¶
Overview¶
A CloudFormation stack set creates EscapeIntegrationReadOnly in every account of an organization or organizational unit (OU). New accounts in that target get the role. Use the same External ID in every account.
The stack set creates the role. Create each Escape integration after that, in Register Each Account.
Prerequisites¶
- Access to the AWS Organizations management account.
- Trusted access between CloudFormation StackSets and AWS Organizations. See Enable trusted access with AWS Organizations.
Setup¶
Copy the External ID from the AWS integration page. You'll paste it into the stack set.
In the AWS CloudFormation console, open StackSets and click Create StackSet. Keep Service-managed permissions.
Choose a Template¶
Save the template below as escape-integration-role.yaml, then upload that file. It sets the role name, so acknowledge CAPABILITY_NAMED_IAM when CloudFormation asks.
AWSTemplateFormatVersion: '2010-09-09'
Description: Cross-account IAM role for the Escape AWS integration
Parameters:
EscapePrincipalArn:
Type: String
Default: arn:aws:iam::178659809426:role/escape-integration
Description: Account trusted to assume this role.
EscapeExternalId:
Type: String
NoEcho: true
MinLength: 2
Description: External ID copied from Escape. Use the same value in every account.
Resources:
EscapeIntegrationRole:
Type: AWS::IAM::Role
Properties:
RoleName: EscapeIntegrationReadOnly
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Ref EscapePrincipalArn
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref EscapeExternalId
Policies:
- PolicyName: EscapeIntegrationRead
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- apigateway:GET
- cloudfront:ListDistributions
- ec2:DescribeAddresses
- elasticloadbalancing:DescribeLoadBalancers
- lambda:GetFunctionUrlConfig
- lambda:ListFunctions
- route53:ListHostedZones
- route53:ListResourceRecordSets
Resource: '*'
Specify StackSet Details¶
- Name the stack set
escape-integration-role - Set
EscapeExternalIdto the External ID you copied - Leave
EscapePrincipalArnas the default
Set Deployment Options¶
- Deploy to the organization, or to one or more OUs
- Leave Automatic deployment enabled so new accounts in the target get the role
- Under Specify regions, select one region,
us-east-1
The stack creates a global IAM role. A second region tries to create the same role name and fails.
Review¶
Open Review and click Submit. Wait until the stack set has created the role in each account before you register them in Escape.
AWS CLI¶
From the management account, with the template file in the current directory:
export EXTERNAL_ID="<External ID from Escape>"
export REGION=us-east-1
aws cloudformation activate-organizations-access
ROOT_ID="$(aws organizations list-roots --query 'Roots[0].Id' --output text)"
aws cloudformation create-stack-set \
--stack-set-name escape-integration-role \
--template-body file://escape-integration-role.yaml \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false \
--capabilities CAPABILITY_NAMED_IAM \
--parameters ParameterKey=EscapeExternalId,ParameterValue="$EXTERNAL_ID"
aws cloudformation create-stack-instances \
--stack-set-name escape-integration-role \
--deployment-targets OrganizationalUnitIds="$ROOT_ID" \
--regions "$REGION"
Replace OrganizationalUnitIds with an OU ID (ou-xxxx-xxxxxxxx) to limit which accounts get the role. Child OUs are included.
Register Each Account¶
After the stack set has created the roles, create one Escape integration per account. This script requires AWS CLI v2, jq, and an authenticated escape-cli. See Getting Started.
: "${EXTERNAL_ID:?Set EXTERNAL_ID first}"
accounts="$(aws organizations list-accounts --output json)"
existing="$(escape-cli integrations list --kind aws)"
aws cloudformation list-stack-instances \
--stack-set-name escape-integration-role \
--output json \
| jq -r --argjson accounts "$accounts" --arg region "${REGION:-us-east-1}" '
($accounts.Accounts | map({key: .Id, value: .Name}) | from_entries) as $names
| .Summaries[]
| select(.Region == $region and .Status == "CURRENT")
| [.Account, $names[.Account]]
| @tsv
' \
| while IFS=$'\t' read -r account_id account_name; do
if grep -Fq "(${account_id})" <<<"$existing"; then
echo "Skipping ${account_id}: integration already exists"
continue
fi
jq -n \
--arg name "${account_name} (${account_id})" \
--arg role_arn "arn:aws:iam::${account_id}:role/EscapeIntegrationReadOnly" \
--arg external_id "$EXTERNAL_ID" \
'{name: $name, parameters: {external_integration_role_arn: $role_arn, external_id: $external_id}}' \
| escape-cli integrations create --kind aws
done
When a new account joins the target, run the loop again.
Uninstall¶
Delete the stack instances, then the stack set. See Delete a stack set. Delete the matching integrations in Escape.
Programmatic Setup¶
Public API¶
Create an integration with POST /v3/integrations/aws:
curl -X POST https://public.escape.tech/v3/integrations/aws \
-H "X-ESCAPE-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production AWS",
"parameters": {
"external_integration_role_arn": "arn:aws:iam::123456789012:role/EscapeIntegrationReadOnly",
"external_id": "escape-00000000-0000-0000-0000-000000000000"
}
}'
| Field | Description |
|---|---|
external_integration_role_arn |
ARN of the IAM role in your account |
external_id |
External ID in that role's trust policy. Reuse one value across accounts. |
See the OpenAPI specification (createawsIntegration, listawsIntegrations) for the full request and response schemas.
CLI¶
escape-cli integrations create --kind aws <<'EOF'
{
"name": "Production AWS",
"parameters": {
"external_integration_role_arn": "arn:aws:iam::123456789012:role/EscapeIntegrationReadOnly",
"external_id": "escape-00000000-0000-0000-0000-000000000000"
}
}
EOF
Use the External ID from the trust policy. The same value can be stored on every AWS integration in the organization.