Accessing the AWS Transform WebApp from a VPC
When you use AWS PrivateLink to access the AWS Transform API privately from your VPC, the webapp requires additional network configuration. It serves static content (HTML, JavaScript, CSS) through CloudFront, which requires internet connectivity. API calls from the webapp go through your VPC endpoint and remain fully private.
This guide shows you how to configure controlled internet egress from your VPC so the webapp can load while keeping your VPC locked down to only the required domains.
How it works
The AWS Transform WebApp uses two network paths:
-
API calls – When you interact with the webapp (starting jobs, viewing workspaces, and so on), the browser sends API requests to
api.transform.. With AWS PrivateLink configured, these requests resolve to a private IP address in your VPC and never leave the AWS network.region.on.aws -
Static content – The webapp's HTML, JavaScript, and CSS files are served through CloudFront via
. Loading these files requires internet connectivity because CloudFront content delivery is only available over the public internet.tenant-id.transform.region.on.aws -
Authentication – AWS IAM Identity Center sign-in flows use
, which also requires internet connectivity.region.signin.aws
To enable the webapp while maintaining security, you create a controlled egress path using AWS Network Firewall with domain-based filtering. This allows your VPC to reach only the specific domains required by the webapp while blocking all other internet traffic.
Architecture
The following diagram shows the network path for webapp traffic:
EC2 Instance / Workspace (Private Subnet) | | Route: 0.0.0.0/0 → Network Firewall Endpoint v AWS Network Firewall (Firewall Subnet) | Allows: *.cloudfront.net, *.transform.<region>.on.aws, | <region>.signin.aws, SSO domains, S3 presigned URLs | Blocks: everything else (TLS SNI inspection) | | Route: 0.0.0.0/0 → NAT Gateway v NAT Gateway (Public Subnet) | | Route: 0.0.0.0/0 → Internet Gateway v Internet Gateway → CloudFront Edge Locations
Important
The Network Firewall must see both directions of traffic (symmetric routing) to perform TLS SNI inspection. You must configure a return route in the NAT Gateway subnet that sends traffic destined for the private subnet back through the firewall. Without this, domain-based filtering rules will not work.
Prerequisites
Before you begin, ensure you have:
-
An AWS account with permissions to create VPC resources, Network Firewall, and NAT Gateways.
-
A VPC with a private subnet where your instances or workloads run.
-
An internet gateway attached to the VPC (or permissions to create one).
-
A AWS Transform VPC endpoint configured. For instructions, see AWS Transform and interface endpoints (AWS PrivateLink).
Setting up controlled internet egress
Complete the following steps to configure Network Firewall with domain-based filtering for the AWS Transform WebApp.
Step 1: Create the firewall subnet
Create a small /28 subnet dedicated to the Network Firewall endpoint. This subnet must be in the same Availability Zone as your private subnet.
aws ec2 create-subnet \ --vpc-idyour-vpc-id\ --cidr-blockfirewall-subnet-cidr\ --availability-zoneyour-az\ --regionregion
Step 2: Create a public subnet for the NAT Gateway
aws ec2 create-subnet \ --vpc-idyour-vpc-id\ --cidr-blockpublic-subnet-cidr\ --availability-zoneyour-az\ --regionregion
Step 3: Configure the public subnet route table
Create a route table for the public subnet that routes internet traffic to the internet gateway.
# Create route table PUB_RTB=$(aws ec2 create-route-table \ --vpc-idyour-vpc-id\ --regionregion\ --query 'RouteTable.RouteTableId' --output text) # Add default route to internet gateway aws ec2 create-route \ --route-table-id $PUB_RTB \ --destination-cidr-block 0.0.0.0/0 \ --gateway-idyour-igw-id\ --regionregion# Associate with public subnet aws ec2 associate-route-table \ --route-table-id $PUB_RTB \ --subnet-idpublic-subnet-id\ --regionregion
Step 4: Create the NAT Gateway
# Allocate an Elastic IP EIP=$(aws ec2 allocate-address --domain vpc \ --regionregion--query 'AllocationId' --output text) # Create NAT Gateway in the public subnet NAT_ID=$(aws ec2 create-nat-gateway \ --subnet-idpublic-subnet-id\ --allocation-id $EIP \ --regionregion\ --query 'NatGateway.NatGatewayId' --output text) # Wait for NAT Gateway to become available (~2 minutes) aws ec2 wait nat-gateway-available \ --nat-gateway-ids $NAT_ID --regionregion
Step 5: Create the Network Firewall rule group
Create a stateful rule group that allows traffic only to the domains required by the AWS Transform WebApp.
aws network-firewall create-rule-group \ --rule-group-name transform-webapp-domains \ --type STATEFUL \ --capacity 100 \ --rule-group '{ "StatefulRuleOptions": { "RuleOrder": "STRICT_ORDER" }, "RulesSource": { "RulesSourceList": { "Targets": [ ".cloudfront.net", ".transform.region.on.aws", "region.signin.aws", ".s3.region.amazonaws.com", "oidc.region.amazonaws.com", "portal.sso.region.amazonaws.com", "assets.sso-portal.region.amazonaws.com", "directory-id.awsapps.com" ], "TargetTypes": ["TLS_SNI", "HTTP_HOST"], "GeneratedRulesType": "ALLOWLIST" } } }' \ --regionregion
Replace region with the AWS Region where your AWS Transform
profile is installed (for example, us-east-1). Replace
directory-id with your IAM Identity Center directory
ID (for example, d-1234567890). You can find your directory ID in the
IAM Identity Center console.
The following table explains the allowed domains.
| Domain | Purpose |
|---|---|
.cloudfront.net |
CloudFront CDN – serves webapp static assets (JavaScript, CSS, images) |
.transform. |
Webapp tenant URL – the browser loads the initial page from this domain via CloudFront |
|
SSO sign-in redirect page |
.s3. |
S3 presigned URLs – artifact uploads and downloads |
oidc. |
OIDC token exchange for SSO authentication |
portal.sso. |
SSO portal login page |
assets.sso-portal. |
SSO portal static assets (CSS, JavaScript) |
|
IAM Identity Center portal for your organization |
Note
The .cloudfront.net wildcard allows traffic to any CloudFront
distribution, not only the AWS Transform WebApp's. A narrower domain filter is not
possible because CloudFront edge IPs are shared across distributions and TLS SNI
inspection cannot distinguish individual distributions behind the same
domain.
Note
API calls to
api.transform. go
through AWS PrivateLink and do not require internet egress. They are not affected
by the firewall.region.on.aws
Step 6: Create the firewall policy
The policy must use STRICT_ORDER rule evaluation with
drop_established as the default action. This ensures that any
traffic not matching the allowlist is dropped.
# Get the rule group ARN RG_ARN=$(aws network-firewall describe-rule-group \ --rule-group-name transform-webapp-domains \ --type STATEFUL --regionregion\ --query 'RuleGroupResponse.RuleGroupArn' --output text) # Create the firewall policy aws network-firewall create-firewall-policy \ --firewall-policy-name transform-webapp-policy \ --firewall-policy "{ \"StatelessDefaultActions\": [\"aws:forward_to_sfe\"], \"StatelessFragmentDefaultActions\": [\"aws:forward_to_sfe\"], \"StatefulRuleGroupReferences\": [ { \"ResourceArn\": \"$RG_ARN\", \"Priority\": 1 } ], \"StatefulEngineOptions\": { \"RuleOrder\": \"STRICT_ORDER\" }, \"StatefulDefaultActions\": [\"aws:drop_established\", \"aws:alert_established\"] }" \ --regionregion
Step 7: Create the Network Firewall
# Get the policy ARN FW_POLICY_ARN=$(aws network-firewall describe-firewall-policy \ --firewall-policy-name transform-webapp-policy \ --regionregion\ --query 'FirewallPolicyResponse.FirewallPolicyArn' --output text) # Create the firewall aws network-firewall create-firewall \ --firewall-name transform-webapp-firewall \ --firewall-policy-arn $FW_POLICY_ARN \ --vpc-idyour-vpc-id\ --subnet-mappings SubnetId=firewall-subnet-id\ --regionregion# Wait for the firewall to become READY (3-5 minutes) while true; do STATUS=$(aws network-firewall describe-firewall \ --firewall-name transform-webapp-firewall \ --regionregion\ --query 'FirewallStatus.Status' --output text) echo "Status: $STATUS" if [ "$STATUS" = "READY" ]; then break; fi sleep 15 done
Step 8: Get the firewall endpoint ID
FW_ENDPOINT=$(aws network-firewall describe-firewall \ --firewall-name transform-webapp-firewall \ --regionregion\ --query "FirewallStatus.SyncStates.\"your-az\".Attachment.EndpointId" \ --output text) echo "Firewall endpoint: $FW_ENDPOINT"
Step 9: Configure the firewall subnet route table
Route internet-bound traffic from the firewall subnet to the NAT Gateway.
FW_RTB=$(aws ec2 create-route-table \ --vpc-idyour-vpc-id\ --regionregion\ --query 'RouteTable.RouteTableId' --output text) aws ec2 create-route \ --route-table-id $FW_RTB \ --destination-cidr-block 0.0.0.0/0 \ --nat-gateway-id $NAT_ID \ --regionregionaws ec2 associate-route-table \ --route-table-id $FW_RTB \ --subnet-idfirewall-subnet-id\ --regionregion
Step 10: Update the private subnet route table
Route all internet-bound traffic from the private subnet through the firewall.
aws ec2 create-route \ --route-table-idprivate-subnet-route-table-id\ --destination-cidr-block 0.0.0.0/0 \ --vpc-endpoint-id $FW_ENDPOINT \ --regionregion
If a default route already exists, use replace-route instead of
create-route.
Step 11: Add the symmetric return route (required)
Important
This step is critical. Network Firewall uses TLS SNI inspection and must see both directions of a TCP connection. Add a route in the NAT Gateway subnet's route table that sends return traffic destined for the private subnet back through the firewall.
aws ec2 create-route \ --route-table-id $PUB_RTB \ --destination-cidr-blockprivate-subnet-cidr\ --vpc-endpoint-id $FW_ENDPOINT \ --regionregion
Replace private-subnet-cidr with the CIDR block of your
private subnet (for example, 10.0.144.0/20).
Without symmetric routing, the firewall only sees one direction of traffic. The TLS inspection engine cannot extract the Server Name Indication (SNI) from the TLS handshake, and all domain-based rules will fail silently.
Step 12: Remove the IPv6 default route (if present)
If the private subnet route table has an IPv6 default route
(::/0) pointing directly to an internet gateway, it will bypass the
firewall for IPv6-capable destinations. Remove it:
aws ec2 delete-route \ --route-table-idprivate-subnet-route-table-id\ --destination-ipv6-cidr-block ::/0 \ --regionregion
Verification
From an instance in the private subnet, verify the configuration:
# Should SUCCEED - webapp content via CloudFront (allowed) curl -vL --connect-timeout 15 \ 'https://tenant-id.transform.region.on.aws' # Should SUCCEED - API via PrivateLink (does not use firewall) curl -v --connect-timeout 15 \ 'https://api.transform.region.on.aws/' # Should FAIL - non-allowlisted domain (blocked by firewall) curl -v --connect-timeout 15 'https://www.example.com' # Expected: TLS connection error (firewall drops after SNI inspection)
Troubleshooting
- The webapp does not load (connection timeout)
-
-
Verify the private subnet route table has a
0.0.0.0/0route pointing to the firewall endpoint. -
Verify the firewall subnet route table has a
0.0.0.0/0route pointing to the NAT Gateway. -
Verify the NAT Gateway is in an
availablestate.
-
- Non-allowlisted domains are not blocked
-
-
Check for an IPv6
::/0route pointing to the internet gateway. This bypasses the firewall. Remove it (Step 12). -
Verify symmetric routing is configured. The NAT Gateway subnet route table must have a return route through the firewall for the private subnet CIDR (Step 11).
-
Verify the firewall policy uses
STRICT_ORDERwithdrop_establishedandalert_establishedas default actions.
-
Cost considerations
| Resource | Approximate cost |
|---|---|
| Network Firewall | ~$0.395/hr (~$288/month) per Availability Zone |
| NAT Gateway | ~$0.045/hr (~$33/month) + data processing fees |
| Elastic IP (public IPv4) | ~$0.005/hr (~$3.60/month) |
| Network Firewall data processing | $0.065/GB |
| NAT Gateway data processing | $0.045/GB |
Estimated base cost is approximately $325/month for a single Availability Zone deployment. For production deployments, deploy the firewall, NAT Gateway, and associated subnets in each Availability Zone where private subnets exist.
Cleanup
To remove all resources created by this guide, run the following commands in reverse
order. Replace the placeholders with the resource IDs from your deployment. You can find
these values in the AWS Management Console or by using the describe
commands from the setup steps.
# Remove return route from NAT Gateway subnet aws ec2 delete-route --route-table-idpub-rtb-id\ --destination-cidr-blockprivate-subnet-cidr\ --regionregion# Remove route from private subnet aws ec2 delete-route \ --route-table-idprivate-subnet-route-table-id\ --destination-cidr-block 0.0.0.0/0 --regionregion# Delete Network Firewall (takes ~5 minutes) aws network-firewall delete-firewall \ --firewall-name transform-webapp-firewall \ --regionregion# Delete firewall policy and rule group aws network-firewall delete-firewall-policy \ --firewall-policy-name transform-webapp-policy \ --regionregionaws network-firewall delete-rule-group \ --rule-group-name transform-webapp-domains \ --type STATEFUL --regionregion# Delete NAT Gateway (wait ~5 minutes for full deletion) aws ec2 delete-nat-gateway --nat-gateway-idnat-gateway-id\ --regionregion# Release Elastic IP aws ec2 release-address --allocation-ideip-allocation-id\ --regionregion# Delete subnets aws ec2 delete-subnet --subnet-idfirewall-subnet-id\ --regionregionaws ec2 delete-subnet --subnet-idpublic-subnet-id\ --regionregion