aws / aws/aws-cdk

ec2: vpc interface endpoint not attaching to selected subnets

Open
#37,144 4 comments 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-ec2 effort/medium feature-request good first issue
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

When I run `cdk synth` the generated yaml template does not use the subnets that I specified for the VPC interface endpoint.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Last Known Working CDK Library Version

_No response_

### Expected Behavior

The generated yaml template will use the subnets that I specify, e.g. the subnet I have provided to the `ec2.SubnetSelection` object, when creating the VPC interface endpoint.

### Current Behavior

Running `cdk synth` generates a yaml template like the following where there are no subnets selected in the `SubnetIds` property. This results in a VPC endpoint that does not work because it is not attached to subnets and there is therefore no route to it.

```yaml
ExampleVpcSSMEndpointB39410AA:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- Fn::GetAtt:
- ExampleVpcSSMEndpointSecurityGroup8A8CCA72
- GroupId
ServiceName:
Fn::Join:
- ""
- - com.amazonaws.
- Ref: AWS::Region
- .ssm
SubnetIds: []
Tags:
- Key: Name
Value: ExampleAppStack/ExampleVpc
VpcEndpointType: Interface
VpcId:
Ref: ExampleVpc7799291B
Metadata:
aws:cdk:path: ExampleAppStack/ExampleVpc/SSMEndpoint/Resource
```

I have other stacks that use the exact same code for subnet selection and they synth correctly, the only difference being is that those stacks only have a single private subnet. My understanding is that it's fine to have more than one private subnet in a VPC, but you can only associate a single subnet per AZ with the VPC interface, so it feels like this should work since I am only selecting a single subnet.

### Reproduction Steps

Example reproduction stack (created from `cdk init app --language python` with the following code pasted into `example_app_stack.py`)

```python
from aws_cdk import (
Stack,
aws_ec2 as ec2
)
from constructs import Construct

class ExampleAppStack(Stack):

def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)

self.vpc = ec2.Vpc(
self,
"ExampleVpc",
ip_addresses=ec2.IpAddresses.cidr("10.0.0.0/16"),
max_azs=3, # Use up to 3 AZs for high availability
nat_gateways=0,
subnet_configuration=[],
create_internet_gateway=False,
enable_dns_support=True,
enable_dns_hostnames=True,
)

self.default_network_acl = ec2.CfnNetworkAcl(
self,
"DefaultNetworkAcl",
vpc_id=self.vpc.vpc_id,
)

self.igw = ec2.CfnInternetGateway(self, "InternetGateway")

# Attach Internet Gateway to VPC
self.igw_attachment = ec2.CfnVPCGatewayAttachment(
self,
"IGWAttachment",
vpc_id=self.vpc.vpc_id,
internet_gateway_id=self.igw.ref,
)

# Ensure IGW is created before attachment
self.igw_attachment.add_dependency(self.igw)

# Create public subnet
self.public_subnet = ec2.CfnSubnet(
self,
"PublicSubnet",
vpc_id=self.vpc.vpc_id,
cidr_block="10.0.1.0/24",
availability_zone="ap-southeast-2a",
map_public_ip_on_launch=False,
)

self.eip = ec2.CfnEIP(self, "NATGatewayEIP", domain="vpc")

self.nat_gateway = ec2.CfnNatGateway(
self,
"NATGateway",
subnet_id=self.public_subnet.ref,
allocation_id=self.eip.attr_allocation_id,
)

self.public_route_table = ec2.CfnRouteTable(
self,
"PublicRouteTable",
vpc_id=self.vpc.vpc_id,
)

self.public_route = ec2.CfnRoute(
self,
"PublicRoute",
route_table_id=self.public_route_table.ref,
destination_cidr_block="0.0.0.0/0",
gateway_id=self.igw.ref,
)
self.public_route.add_dependency(self.igw_attachment)

ec2.CfnSubnetRouteTableAssociation(
self,
"PublicSubnetRouteTableAssociation",
subnet_id=self.public_subnet.ref,
route_table_id=self.public_route_table.ref,
)

# Create private subnets
self.private_subnet_a = ec2.CfnSubnet(
self,
id="PrivateSubnetA",
vpc_id=self.vpc.vpc_id,
cidr_block="10.0.2.0/24",
availability_zone="ap-southeast-2a",
map_public_ip_on_launch=False,
)

self.private_subnet_b = ec2.CfnSubnet(
self,
id="PrivateSubnetB",
vpc_id=self.vpc.vpc_id,
cidr_block="10.0.3.0/24",
availability_zone="ap-southeast-2a",
map_public_ip_on_launch=False,
)

self.private_route_table = ec2.CfnRouteTable(
self,
"PrivateRouteTable",
vpc_id=self.vpc.vpc_id,
)
self.private_route = ec2.CfnRoute(
self,
"PrivateRoute",
route_table_id=self.private_route_table.ref,
destination_cidr_block="0.0.0.0/0",
nat_gateway_id=self.nat_gateway.ref,
)
# Ensure the NAT Gateway is created before creating the route
self.private_route.add_dependency(self.nat_gateway)
# Associate private route table with private subnets
ec2.CfnSubnetRouteTableAssociation(
self,
"PrivateSubnetARouteTableAssociation",
subnet_id=self.private_subnet_a.ref,
route_table_id=self.private_route_table.ref,
)
ec2.CfnSubnetRouteTableAssociation(
self,
"PrivateSubnetBRouteTableAssociation",
subnet_id=self.private_subnet_b.ref,
route_table_id=self.private_route_table.ref,
)

# SSM endpoint
self.ssm_endpoint = self.vpc.add_interface_endpoint(
"SSMEndpoint",
service=ec2.InterfaceVpcEndpointAwsService.SSM,
subnets=ec2.SubnetSelection(subnets=[self.private_subnet_a]),
)

# SSM Messages endpoint
self.ssm_messages_endpoint = self.vpc.add_interface_endpoint(
"SSMMessagesEndpoint",
service=ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
subnets=ec2.SubnetSelection(subnets=[self.private_subnet_a]),
)

# EC2 Messages endpoint
self.ec2_messages_endpoint = self.vpc.add_interface_endpoint(
"EC2MessagesEndpoint",
service=ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
subnets=ec2.SubnetSelection(subnets=[self.private_subnet_a]),
)
```

### Possible Solution

_No response_

### Additional Information/Context

requirements.txt has the following versions:

```
aws-cdk-lib>=2.239.0,<3.0.0
constructs>=10.5.0,<11.0.0
```

### AWS CDK Library version (aws-cdk-lib)

2.241.0

### AWS CDK CLI version

2.1107.0 (build e51b1ae)

### Node.js Version

v24.11.1

### OS

Ubuntu-24.04 running in windows subsystem for linux

### Language

Python

### Language Version

3.10.19

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with the reproduction in example_app_stack.py and the Vpc.add_interface_endpoint entry point, then run cdk synth and inspect the generated AWS CloudFormation template. Done means each interface endpoint's SubnetIds contains the selected private_subnet_a instead of an empty list.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, python, typescript
Domain
cloud, infrastructure, networking
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.