aws/aws-cdk

ec2: vpc interface endpoint not attaching to selected subnets

Open

#37144 opened on Mar 3, 2026

View on GitHub
 (4 comments) (0 reactions) (0 assignees)TypeScript (10,710 stars) (3,530 forks)batch import
@aws-cdk/aws-ec2effort/mediumfeature-requestgood first issue

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.

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)

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