Using CDK how to get a full list of a VPC endpoint's network interface IDs?

0

I'm setting some internal static sites in s3 that I want to access with https. To achieve this I have set up an ALB in front of an S3 VPC Interface endpoint to access the buckets from within our VPC over https. I am using CDK (typescript).

In order to create the target group for the alb I need the vpc endpoint's network interfaces IP addresses. These are not available to me as far as I can tell within CDK.

If I have the network interface IDs, which are/should be available to me, I can set up a custom resource to get the private IP address for each, and then use those for the alb target group.

as is shown here: https://repost.aws/questions/QUjISNyk6aTA6jZgZQwKWf4Q/how-to-connect-a-load-balancer-and-an-interface-vpc-endpoint-together-using-cdk

e.g.,

    const vpce = vpc.addInterfaceEndpoint('s3-vpce', {
      service: InterfaceVpcEndpointAwsService.S3,
      subnets: {
        subnetType: SubnetType.PRIVATE_WITH_EGRESS // This should be PRIVATE_ISOLATED unrelated
      },
      open: false,
      securityGroups: [vpceSg],
    });
    const outputPaths = vpce.vpcEndpointNetworkInterfaceIds.map((_, index) => `NetworkInterfaces.${index}.PrivateIpAddress`);
    const enis = new AwsCustomResource(this, `vpce-describe-enis`, {
        onCreate: {
          service: "EC2",
          action: "describeNetworkInterfaces",
          parameters: {
            NetworkInterfaceIds: vpce.vpcEndpointNetworkInterfaceIds,
          },
          physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
          outputPaths: outputPaths
        },
        onUpdate: {
          service: "EC2",
          action: "describeNetworkInterfaces",
          parameters: {
            NetworkInterfaceIds: vpce.vpcEndpointNetworkInterfaceIds,
          },
          physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
          outputPaths: outputPaths
        },
        policy: {
          statements: [
            new PolicyStatement({
              actions: ["ec2:DescribeNetworkInterfaces"],
              resources: ["*"],
            }),
          ],
        },
      }
    );

    const ipAddresses = outputPaths.map(path => enis.getResponseField(path));

The issue I have though, is that the addInterfaceEndpoint call seems to return before all the network interfaces are assigned to the vpc endpoint.

I consistently only have one network interface available in vpce.vpcEndpointNetworkInterfaceIds when I should have three (the vpc has 3 PRIVATE_WITH_EGRESS subnets). With only one resulting IP address to use in the target group for the alb.

When I review describe the vpc endpoint manually after the cdk deploy is finished I do see all three expected network interface IDs.

Is there a better way to be creating/adding the endpoint such that I get a representation of the fully created endpoint back for use within the rest of my stack? Or is there a way to load the endpoint after creation such that I can get all network interfaces for the endpoint?

I see there is InterfaceVpcEndpoint.fromInterfaceVpcEndpointAttributes() but that

  1. asks for a port parameter for the endpoint service, which for s3 or indeed any other service I'm not sure what I would provide; and
  2. it returns an IInterfaceVpcEndpointwhich does not seem to include vpcEndpointNetworkInterfaceIds or many other fields that are present on InterfaceVpcEndpoint.

Thanks in advance!

eoin

1 Answer
0

The issue you're experiencing with the VPC endpoint network interfaces not being fully available immediately after creation is a common challenge when working with AWS CDK and VPC endpoints. This is because the creation of network interfaces for VPC endpoints is an asynchronous process that may not complete by the time the CDK construct returns.

To address this problem and obtain all the network interface IDs for your VPC endpoint, you can use a custom resource approach similar to what you've already shown, but with a slight modification to ensure you get all the interfaces. Here's an improved approach:

  1. Create the VPC endpoint as you're currently doing.
  2. Use a custom resource to describe the VPC endpoint itself, rather than the network interfaces directly.
  3. Extract the network interface IDs from the VPC endpoint description.
  4. Use another custom resource to describe these network interfaces and get their private IP addresses.

Here's how you could implement this:

const vpce = vpc.addInterfaceEndpoint('s3-vpce', {
  service: InterfaceVpcEndpointAwsService.S3,
  subnets: {
    subnetType: SubnetType.PRIVATE_WITH_EGRESS
  },
  open: false,
  securityGroups: [vpceSg],
});

// Custom resource to describe the VPC endpoint
const describeVpce = new AwsCustomResource(this, 'DescribeVpce', {
  onCreate: {
    service: 'EC2',
    action: 'describeVpcEndpoints',
    parameters: {
      VpcEndpointIds: [vpce.vpcEndpointId]
    },
    physicalResourceId: PhysicalResourceId.of(Date.now().toString())
  },
  policy: AwsCustomResourcePolicy.fromSdkCalls({resources: AwsCustomResourcePolicy.ANY_RESOURCE})
});

// Extract network interface IDs
const networkInterfaceIds = describeVpce.getResponseField('VpcEndpoints.0.NetworkInterfaceIds');

// Custom resource to describe network interfaces
const describeNetworkInterfaces = new AwsCustomResource(this, 'DescribeNetworkInterfaces', {
  onCreate: {
    service: 'EC2',
    action: 'describeNetworkInterfaces',
    parameters: {
      NetworkInterfaceIds: networkInterfaceIds
    },
    physicalResourceId: PhysicalResourceId.of(Date.now().toString())
  },
  policy: AwsCustomResourcePolicy.fromSdkCalls({resources: AwsCustomResourcePolicy.ANY_RESOURCE})
});

// Extract private IP addresses
const privateIpAddresses = describeNetworkInterfaces.getResponseField('NetworkInterfaces[*].PrivateIpAddress');

// Now you can use privateIpAddresses for your ALB target group

This approach ensures that you get all the network interfaces associated with the VPC endpoint, even if they're not immediately available when the endpoint is first created. The custom resources will wait for the VPC endpoint to be fully provisioned before attempting to describe its network interfaces.

Regarding your questions about InterfaceVpcEndpoint.fromInterfaceVpcEndpointAttributes():

  1. The port parameter is indeed not applicable for S3 VPC endpoints, as S3 doesn't use a specific port for VPC endpoint connections.
  2. You're correct that IInterfaceVpcEndpoint doesn't include vpcEndpointNetworkInterfaceIds. This interface is designed to be a minimal representation of a VPC endpoint, suitable for referencing existing endpoints but not for detailed management.

The custom resource approach outlined above should provide you with the necessary information to create your ALB target group with all the VPC endpoint's network interface IP addresses.
Sources
interface VpcConfigProperty · AWS CDK
interface VpcOptionsProperty · AWS CDK
interface VpcConfigProperty · AWS CDK
interface EndpointDetailsProperty · AWS CDK

profile picture
answered 5 days ago
  • On the face of it this the answer looks very compelling. However I think there is one core issue which i'm not sure we can work around with the current approach.

    AwsCustomResource.getResponseField only works on primatives and I think can only return one value at a time. So in your example where you have describeVpce.getResponseField('VpcEndpoints.0.NetworkInterfaceIds') and describeNetworkInterfaces.getResponseField('NetworkInterfaces[*].PrivateIpAddress') these statements do not work since those keys don't exist in the custom resource response.

    The only way to get it work was to know ahead of time how many NetworkInterfaceIds to expect, e.g. 3, and iterate over them, and likewise then for the PrivateIpAddresses, i.e.,

        const networkInterfaceIds = [0,1,2].map(index => describeVpce.getResponseField(`VpcEndpoints.0.NetworkInterfaceIds.${index}`));
    ...
        const privateIpAddresses = [0,1,2].map(index => describeNetworkInterfaces.getResponseField(`NetworkInterfaces.${index}.PrivateIpAddress`));
    

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions